/cross-platform-parsing - Cross-Platform Text and CLI Parsing
Activate when writing code that parses CLI output, processes text files, or runs shell commands in a cross-platform (Windows + macOS/Linux) context. These patterns prevent silent data corruption from line-ending and shell-interpretation differences.
Steps
1. CRLF-Safe Regex for Line Endings
Never use /\n/ in regex patterns. Use /\r?\n/ to handle both Windows CRLF and Unix LF:
// Wrong: fails silently on Windows files
const parts = text.split('\n')
const hasFrontmatter = /^---\n/.test(content)
// Correct
const parts = text.split(/\r?\n/)
const hasFrontmatter = /^---\r?\n/.test(content)
This applies to: frontmatter delimiters, line splitting, multiline regex patterns, and any file read from disk on Windows.
2. execFileSync Instead of execSync for Special Characters
On Windows, execSync routes through cmd.exe which interprets %, ^, !, and & as shell metacharacters. Git format strings, path patterns, and many CLI flags contain these characters.
Use execFileSync(command, argsArray) to bypass shell interpretation entirely:
import { execFileSync } from 'child_process'
// Wrong: cmd.exe eats the % characters
execSync('git log --format="%H|%s|%an|%ai"')
// Correct: args passed directly without shell
execFileSync('git', ['log', '--format=%H|%s|%an|%ai'])
3. Never .trim() Positional CLI Output
CLI tools like git status --porcelain use leading whitespace as semantic codes. .trim() corrupts these:
// Wrong: strips the staging status code from " M filename.ts"
const line = rawLine.trim()
// Correct: strip only trailing newlines
const line = rawLine.replace(/[\r\n]+$/, '')
// For splitting into lines, filter empty rather than trimming each
const lines = output.replace(/[\r\n]+$/, '').split(/\r?\n/).filter(Boolean)
Formats with positional whitespace: git status --porcelain, git diff --stat, column-aligned output.
4. set -a Before Sourcing Secrets in MCP Wrapper Scripts
MCP wrapper scripts that launch npx or uv run against a sourced secrets.env must export every variable to child processes, not just set them in the current shell. Without set -a, the variables are assigned but not exported, and the MCP subprocess sees nothing.
#!/usr/bin/env bash
# Wrong: vars set in shell scope only; npx child sees nothing
source "$HOME/.claude/secrets/secrets.env"
exec npx -y @org/mcp-server
# Correct: set -a marks every subsequent assignment for export
set -a
source "$HOME/.claude/secrets/secrets.env"
set +a
exec npx -y @org/mcp-server
Rule: every MCP wrapper under ~/.claude/scripts/ that sources secrets.env must bracket the source with set -a / set +a (or equivalent export VAR=... assignments). If a fresh MCP says it cannot read its key, check the wrapper first.
5. Bash Tool Shell Is zsh — Word-Splitting Differs
The Claude Code Bash tool executes commands in zsh, not bash. This creates two silent failure modes:
Mode A: Unquoted variable word-splitting. In bash, for f in $list splits $list on IFS. In zsh, unquoted variables are NOT word-split by default, so the loop iterates once over the entire string as a single token. A payload copy loop that silently processed a single bogus path instead of many files is the classic symptom.
# Appears to work in bash, silently broken in zsh (Bash tool):
for f in $FILES; do cp "$f" "$DEST/"; done
# Safe in both: pass through bash explicitly or use an array
bash -c 'for f in '"$FILES"'; do cp "$f" "$DEST/"; done'
# Or, in scripts with a bash shebang, arrays work correctly:
for f in "${FILES[@]}"; do cp "$f" "$DEST/"; done
Mode B: Interactive-shell errors are red herrings for bash-shebang scripts. source-ing a bash library in the Bash tool's zsh shell may produce errors (e.g., zsh-style read -A failures) even though the script runs correctly under its #!/usr/bin/env bash shebang. bats test suites also run under real bash. Before diagnosing a "broken" script from interactive output, re-run it under the target shell:
bash -c 'source ./lib.sh && test_function'
# Or run the script directly so the shebang takes effect:
./scripts/my-script.sh
Evidence: CJClaudin_Setup session 3f16f8dd (2026-05-24). "The copy loop didn't word-split (the tool's shell is zsh, which doesn't split unquoted vars like bash). / My source ran under the tool's interactive shell (zsh-style read -a failure), not bash. The bats suite (10/10) runs under real bash."
6. Bootstrap and Installer Step Loops Require set -ue
When a bootstrap or installer script iterates over numbered step scripts, use set -ue (not just set -uo pipefail). Without -e, a failing step does not halt the loop, so later steps run against a blank or broken environment.
#!/usr/bin/env bash
# Wrong: a failure in step 02 lets steps 03-08 run against a broken environment
set -uo pipefail
for step in steps/*.sh; do
bash "$step"
done
# Correct: exit immediately on any step failure
set -ueo pipefail
for step in steps/*.sh; do
bash "$step"
done
Also test the partial-run path (--only <step>) to confirm it exits nonzero when the targeted step fails. Both the full run and the partial run must fail fast.
Evidence: CJClaudin_Setup bootstrap.sh used set -uo pipefail without -e. A failure in step 02 (repo clone) would have let steps 03-08 configure MCP servers and verify against an empty checkout. Caught in Phase 5 code review.
Source Instincts
use-crlf-safe-regex: "when writing regex to match line endings in cross-platform code"use-execfilesync-on-windows: "when using execSync with format strings containing %, ^, !, or & on Windows"no-trim-on-structured-cli-output: "when parsing structured CLI output where whitespace has semantic meaning"set-a-before-source-secrets: "when writing an MCP wrapper script that sources secrets.env before launching npx or uv run"verify-under-target-shell: "when a shell script misbehaves in the harness shell or an interactive shell"bootstrap-step-loop-set-e: "when writing bootstrap/installer scripts that run numbered steps in a loop"