shell-portability
Skill0 starsChanged 45 days agoMITView on GitHub
Read-only scan for cross-shell traps with PS-5.1-safe fixes: && and ||, PS7-only syntax, blocking prompts, missing -Encoding, bash-isms in .ps1. check "<cmd>" tests one command. Use when: "will this run on PowerShell 5.1", "lint my shell scripts".
- Commits and pushes
In this repository34 other files it gives its agents.
- audit-codeSkill
- audit-docsSkill
- auditSkill
- bisect-driverSkill
- coding-conventionsSkill
- commit-gateSkill
- compact-ioSkill
- cron-task-manageSkill
- cve-auditSkill
- data-integrity-auditSkill
- decision-logSkill
- deck-builderSkill
- determinism-guardSkill
- etl-validateSkill
- experiment-logSkill
- fetch-firstSkill
- flaky-test-detectorSkill
- github-repo-polishSkill
- golden-lockSkill
- history-leak-scanSkill
- landing-checkSkill
- llm-eval-harnessSkill
- local-secrets-manageSkill
- long-taskSkill
- milestone-trackSkill
- opus-workersSkill
- path-quirk-auditSkill
- reorg-proposalSkill
- research-briefSkill
- seed-controlSkill
- skill-vetSkill
- small-taskSkill
- token-squeezeSkill
- venue-fitSkill
The file
We split this skill into the rules it gives an agent
2 rules. About 0 are common, 1 is distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 17
Read-only; it never writes outside its own canary temp dir.
- Very specific: line 131
Read-only — writes nothing except inside a throwaway temp dir during --canary.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
shell-portability — cross-shell syntax trap scanner
The engine is shell-portability.js (portable Node, zero deps). It is the
proactive counterpart to a reactive shell-troubleshooting runbook (that
runbook diagnoses a script that already broke; this scans the source and flags
the same documented traps before they reach the shell) — with file:line,
why it breaks on a Windows machine, and the PowerShell-5.1-safe alternative.
Read-only; it never writes outside its own canary temp dir.
Scope is syntax and semantics only. Encoding and filename quirks (non-ASCII
bytes that corrupt a .bat parse, UTF-16 BOMs, builtin-shadowing stray files)
are path-quirk-audit's job — this scanner deliberately does not duplicate
them, and it skips .bat files entirely.
What it flags
.ps1 / .psm1 — the target here is PowerShell 5.1 (the common
Windows default, not PS7):
| Trap | Why it breaks | PS5.1-safe fix |
&& / || | no pipeline-chain operators — parser error | A; if ($?) { B } / A; if (-not $?) { B } |
?: ternary | PS7-only | if/else |
?. / ?[] | null-conditional, PS7-only | guard with if ($null -ne $x) |
?? / ??= | null-coalescing, PS7-only | if ($null -eq $x) { ... } |
Read-Host / pause / Out-GridView | block a non-interactive / scheduled run | take input as a param / env / file |
Set-Content/Add-Content/Out-File w/o -Encoding | ANSI/UTF-16 default corrupts UTF-8 for the next reader | add -Encoding utf8 |
bash-style NAME=value / export NAME=value | PS can't assign to a bareword; export isn't a cmdlet | $name = value / $env:NAME = 'value' |
a parameter 5.1 lacks: ConvertFrom-SecureString -AsPlainText, Get-Content/Set-Content -AsByteStream, ForEach-Object -Parallel, ConvertFrom-Json -AsHashtable, ConvertTo-Json -AsArray, Invoke-WebRequest/Invoke-RestMethod -SkipCertificateCheck | parameter binding error (measured absent on 5.1, 2026-09-28) | per-parameter fix in the report |
Any file, and check: the payload of powershell -Command "..." /
pwsh -c '...' is scanned as PowerShell. String masking used to hide it, so a
trap wrapped in -Command reported clean.
.sh / .bash — PowerShell-isms leaking into a POSIX script:
| Trap | Why it breaks | POSIX fix |
$env:NAME | bash has no $env: namespace | $NAME / ${NAME}, set with export |
Verb-Noun cmdlet (Get-ChildItem, Set-Content, ...) | PowerShell cmdlet, not a bash command | POSIX equivalent (ls/find, printf > file) |
Commands
node shell-portability.js scan <path> [<path>...]
node shell-portability.js check "<command>" [--sh]
node shell-portability.js --canary
node shell-portability.js --helpscan recurses any files or directories given (skips
.git,node_modules,.golden,graphify-out), reads every.ps1/.psm1/.sh/.bashfile, and prints one block per finding:path\to\deploy.ps1:12: [chain-and-or] PS 5.1 has no && / || pipeline-chain operators (parser error) fix: A; if ($?) { B } (and) / A; if (-not $?) { B } (or) > git pull && npm run buildExit 1 if anything is flagged, 0 if clean.
check runs the same rule table over ONE command string instead of a file. Use it before handing a command to someone to run. Defaults to the PowerShell 5.1 dialect;
--shchecks it as POSIX instead. Same exit codes.$ node shell-portability.js check 'cd "D:/repo" && npm run build' <command>:1: [chain-and-or] PS 5.1 has no && / || pipeline-chain operators (parser error) fix: A; if ($?) { B } (and) / A; if (-not $?) { B } (or) > cd "D:/repo" && npm run buildALWAYS QUOTE the command. Unquoted, your own shell eats the
&&before Node sees it, so only the first half is scanned and it reports clean — a false negative, the worst outcome for a gate. The clean line echoes exactly what was scanned (clean: no PowerShell 5.1 traps in: cd D:/repo) so that truncation is visible rather than silent.
Why check exists
scan only reads files. A command written into a chat reply, a README, or
a runbook never reaches the scanner's input surface — which is how a
cd X && git push gets handed to a PowerShell 5.1 user with the rule that
would catch it sitting right there, having nothing to run against. check
closes that gap by making a bare string checkable.
Suppression
A trailing # portability-ok comment silences every finding on that line — use
it for a deliberate exception (e.g. a string that merely contains &&, or a
script that is PS7-only on purpose):
$result = git pull && git push # portability-ok (PS7 CI runner only)Examples
- Guard a scheduled job's script before it ships (a monthly rebalance job
runs as a Windows scheduled task — a
Read-Hostor a&&would hang or parser-error unattended):node shell-portability.js scan /path/to/scripts— catches any interactive prompt or PS7-ism before the scheduled run hits it. - Check a helper
.ps1writes UTF-8 for the next tool (Set-Content's ANSI default is a well-known trap): the scanner flags everySet-Content/Out-Filemissing-Encoding utf8. - Scan a
.shyou cross-wrote in a PowerShell headspace: catches a stray$env:PATHorGet-ChildItembefore it silently does nothing under bash.
Windows notes
- PowerShell 5.1 lacks
&&,||, ternary,?., and??— this scanner exists because those keep leaking in from PS7 / bash habits. - Backtick line-continuations are joined into one logical line, so an
-Encodingflag on the following line still counts.<# ... #>block comments and whole-line#comments are not scanned. .batfiles are out of scope — their traps are byte/parse-level, which ispath-quirk-audit's domain.
Storage / exit codes
Read-only — writes nothing except inside a throwaway temp dir during --canary.
0 clean · 1 findings or canary failure · 2 usage error.
Verification (the done-check)
node shell-portability.js --canarySelf-tests both directions in a throwaway temp dir: the documented traps are
CAUGHT (a bad .ps1 with && + a ternary + an unencoded Set-Content yields
exactly 3 findings; ??, ?., Read-Host, bash-assign, and a bad .sh each
caught) AND clean PS5.1-safe / POSIX code stays quiet (0 findings); the check
mode catches a chained handed-over command and passes its chain-free form, in
both dialects, and rejects an empty invocation; a trap inside a
powershell -Command / pwsh -c payload is caught (in .ps1 and .sh), as
is the exact -AsPlainText command that failed on 5.1 on 2026-09-28, while its
5.1-safe replacement is not; plus the # portability-ok suppression and an
end-to-end directory walk. MUST print CANARY PASS 30/30 before you trust a
result.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

