scripts/check-shell-portability.sh selects only **/*.sh in both CI-facing modes, so shell
snippets inside skill markdown are never scanned. Every plugin's skill docs are unscanned today.
Why that matters here
Skill markdown is not documentation about code — for a skill, the markdown IS the executable
surface. An agent reads a shell snippet in a SKILL.md or a context/*.md and runs it. A GNU-only
construct there fails on macOS exactly as it would in a .sh file, and docs/PLUGIN-PHILOSOPHY.md:503
commits this repository to Windows, macOS, and Linux.
So the gate covers the surface where a portability defect is caught by a shell that errors loudly,
and skips the surface where it is executed by an agent that may improvise around the error instead.
Reproduction
Found live, not hypothesized. While implementing #2678, a draft of
plugins/review/skills/fanout/context/fix-pass-mode.md specified stat -c %Y — GNU-only; BSD/macOS
stat takes -f %m and errors on -c.
bash scripts/check-shell-portability.sh --paths plugins/review/skills/fanout/context/fix-pass-mode.md
→ fires: PORTABILITY: plugins/review/skills/fanout/context/fix-pass-mode.md:26 ... stat -c
bash scripts/check-shell-portability.sh origin/main (the CI-facing diff mode)
→ does not fire: No unexcused GNU-only constructs in 1 shell file(s).
The detector works. The selection is what misses.
- Diff mode:
git diff --name-only -- '*.sh'
--all mode: find -name '*.sh'
--paths mode accepts arbitrary files, which is the only reason this was caught at all — and only
because it was run by hand.
CI would not have caught it. It was caught by an agent running the gate manually against a file
the gate does not select.
Scope of the gap
Every SKILL.md, context/*.md, and reference/*.md in the marketplace that carries a shell
snippet. Unknown how many currently contain GNU-only constructs — which is itself the point.
Why this is filed rather than fixed in the PR that found it
Widening the selection to include markdown would newly fail any existing skill doc carrying a
GNU-only snippet. That is a separate change with its own blast radius, and folding it into a
findings-coexistence PR would mix an unrelated repo-wide gate change into a contract change.
Suggested shape (owner's call)
- Measure first. Run the existing detector across all skill markdown with
--paths and count
the hits. That number decides whether this is a one-PR fix or a staged one.
- Widen the selection to skill markdown in both CI-facing modes.
- If the count is large, stage it — new and changed files first, backlog behind an allowlist that
shrinks, rather than a flag day.
Note the detector needs no change; only which files it is pointed at.
Related
scripts/check-shell-portability.shselects only**/*.shin both CI-facing modes, so shellsnippets inside skill markdown are never scanned. Every plugin's skill docs are unscanned today.
Why that matters here
Skill markdown is not documentation about code — for a skill, the markdown IS the executable
surface. An agent reads a shell snippet in a SKILL.md or a
context/*.mdand runs it. A GNU-onlyconstruct there fails on macOS exactly as it would in a
.shfile, anddocs/PLUGIN-PHILOSOPHY.md:503commits this repository to Windows, macOS, and Linux.
So the gate covers the surface where a portability defect is caught by a shell that errors loudly,
and skips the surface where it is executed by an agent that may improvise around the error instead.
Reproduction
Found live, not hypothesized. While implementing #2678, a draft of
plugins/review/skills/fanout/context/fix-pass-mode.mdspecifiedstat -c %Y— GNU-only; BSD/macOSstattakes-f %mand errors on-c.bash scripts/check-shell-portability.sh --paths plugins/review/skills/fanout/context/fix-pass-mode.md→ fires:
PORTABILITY: plugins/review/skills/fanout/context/fix-pass-mode.md:26 ... stat -cbash scripts/check-shell-portability.sh origin/main(the CI-facing diff mode)→ does not fire:
No unexcused GNU-only constructs in 1 shell file(s).The detector works. The selection is what misses.
git diff --name-only -- '*.sh'--allmode:find -name '*.sh'--pathsmode accepts arbitrary files, which is the only reason this was caught at all — and onlybecause it was run by hand.
CI would not have caught it. It was caught by an agent running the gate manually against a file
the gate does not select.
Scope of the gap
Every
SKILL.md,context/*.md, andreference/*.mdin the marketplace that carries a shellsnippet. Unknown how many currently contain GNU-only constructs — which is itself the point.
Why this is filed rather than fixed in the PR that found it
Widening the selection to include markdown would newly fail any existing skill doc carrying a
GNU-only snippet. That is a separate change with its own blast radius, and folding it into a
findings-coexistence PR would mix an unrelated repo-wide gate change into a contract change.
Suggested shape (owner's call)
--pathsand countthe hits. That number decides whether this is a one-PR fix or a staged one.
shrinks, rather than a flag day.
Note the detector needs no change; only which files it is pointed at.
Related
docs/PLUGIN-PHILOSOPHY.md:503— the cross-platform commitment this gate is meant to enforce