What happened
While editing host-setup/linux/install-tools.sh (fixing sudo-timestamp drop-in duplication), I checked for shellcheck with command -v shellcheck, find / -iname shellcheck, pip show shellcheck-py, and apt list --installed. All came up empty, so I told the user "no shellcheck available on this host" and fell back to manual review plus fixture-driven bash tests.
That conclusion was wrong. shellcheck runs in this fleet as a pinned koalaman/shellcheck:stable Docker image, documented in GOVERNANCE.md ("Running the Linters Locally"), OPERATIONS.md, and host-setup/linux/README.md:124 ("Neither this tooling nor its Windows sibling installs ... shellcheck ... That is a decision rather than a gap."). Docker itself was present and working on the host the whole time. When the user pushed back and I ran the documented invocation, it found two real SC1087 findings ($user[...] read as ambiguous array-subscript syntax in two grep patterns) that my manual review and tests had missed.
Root cause
Not a tooling gap. A discoverability gap on the agent side:
fleet-skills:shell-codestyle was available and its own description says it triggers "whenever writing, reviewing, or editing a .sh file" and covers "shellcheck cleanliness." I never invoked it.
CODESTYLE.md:54 names shellcheck cleanliness and points at that skill ("Read the skill for the full rules") but does not inline the actual Docker command, so a reader who reads CODESTYLE.md directly without also opening the skill still has nothing runnable.
- A quick
command -v <tool> check is a very natural first move (for an agent or a human) and it doesn't route through either the skill or GOVERNANCE.md by itself. Nothing sits at the point that check fails to say "absence from PATH is expected here, see GOVERNANCE.md."
This is not shellcheck-specific. The same pattern applies to every other linter the fleet deliberately keeps off the host (markdownlint, cspell, actionlint, editorconfig-checker, PSScriptAnalyzer, ruff via uvx): a command -v miss reads as "not available" unless the checker already knows to look at GOVERNANCE.md's "Running the Linters Locally" section first.
Related to (but not a duplicate of) the closed #671, which recorded the decision itself. This issue is about the decision being unreachable from the natural point of failure, not about the decision being undocumented.
Proposed fixes
Any of these close the gap; I don't think they're mutually exclusive.
- Inline the Docker command in
CODESTYLE.md's Bash paragraph, not just a pointer to the skill, so a reader who consults the carried instruction file directly still gets a runnable command without chasing a link.
- State the general principle somewhere reliably read, not only in a skill whose triggering depends on the model recognizing the moment: a tool missing from
command -v is not evidence a check is unavailable in this fleet, since several are deliberately never installed natively. Check GOVERNANCE.md's "Running the Linters Locally" for a pinned Docker/uvx invocation before reporting a check as skipped or unavailable.
- Do not make this load-bearing on skill-trigger reliability alone. Skill triggering is model-driven and probabilistic. Something that affects whether a real defect gets caught (shellcheck cleanliness) is more robust living in a place every session already reads deterministically (
CODESTYLE.md, GOVERNANCE.md, or the carried AGENTS.md bootstrap section) rather than depending solely on the right skill firing at the right moment.
I'd lean toward (1) combined with (2), since together they mean the answer is reachable whether the agent's first move is reading CODESTYLE.md or just probing for the binary, without requiring the skill to fire at all. Happy to send a PR for either once there's agreement on where it should live.
What happened
While editing
host-setup/linux/install-tools.sh(fixing sudo-timestamp drop-in duplication), I checked for shellcheck withcommand -v shellcheck,find / -iname shellcheck,pip show shellcheck-py, andapt list --installed. All came up empty, so I told the user "no shellcheck available on this host" and fell back to manual review plus fixture-driven bash tests.That conclusion was wrong. shellcheck runs in this fleet as a pinned
koalaman/shellcheck:stableDocker image, documented inGOVERNANCE.md("Running the Linters Locally"),OPERATIONS.md, andhost-setup/linux/README.md:124("Neither this tooling nor its Windows sibling installs ... shellcheck ... That is a decision rather than a gap."). Docker itself was present and working on the host the whole time. When the user pushed back and I ran the documented invocation, it found two real SC1087 findings ($user[...]read as ambiguous array-subscript syntax in two grep patterns) that my manual review and tests had missed.Root cause
Not a tooling gap. A discoverability gap on the agent side:
fleet-skills:shell-codestylewas available and its own description says it triggers "whenever writing, reviewing, or editing a .sh file" and covers "shellcheck cleanliness." I never invoked it.CODESTYLE.md:54names shellcheck cleanliness and points at that skill ("Read the skill for the full rules") but does not inline the actual Docker command, so a reader who readsCODESTYLE.mddirectly without also opening the skill still has nothing runnable.command -v <tool>check is a very natural first move (for an agent or a human) and it doesn't route through either the skill orGOVERNANCE.mdby itself. Nothing sits at the point that check fails to say "absence from PATH is expected here, see GOVERNANCE.md."This is not shellcheck-specific. The same pattern applies to every other linter the fleet deliberately keeps off the host (
markdownlint,cspell,actionlint,editorconfig-checker,PSScriptAnalyzer,ruffviauvx): acommand -vmiss reads as "not available" unless the checker already knows to look atGOVERNANCE.md's "Running the Linters Locally" section first.Related to (but not a duplicate of) the closed #671, which recorded the decision itself. This issue is about the decision being unreachable from the natural point of failure, not about the decision being undocumented.
Proposed fixes
Any of these close the gap; I don't think they're mutually exclusive.
CODESTYLE.md's Bash paragraph, not just a pointer to the skill, so a reader who consults the carried instruction file directly still gets a runnable command without chasing a link.command -vis not evidence a check is unavailable in this fleet, since several are deliberately never installed natively. CheckGOVERNANCE.md's "Running the Linters Locally" for a pinned Docker/uvxinvocation before reporting a check as skipped or unavailable.CODESTYLE.md,GOVERNANCE.md, or the carriedAGENTS.mdbootstrap section) rather than depending solely on the right skill firing at the right moment.I'd lean toward (1) combined with (2), since together they mean the answer is reachable whether the agent's first move is reading
CODESTYLE.mdor just probing for the binary, without requiring the skill to fire at all. Happy to send a PR for either once there's agreement on where it should live.