Skip to content

fix(skills): guard context-injection commands so absent tools or refs can't abort a skill#103

Draft
taminomara wants to merge 1 commit into
testdouble:mainfrom
taminomara:fix/which-availability-guards
Draft

fix(skills): guard context-injection commands so absent tools or refs can't abort a skill#103
taminomara wants to merge 1 commit into
testdouble:mainfrom
taminomara:fix/which-availability-guards

Conversation

@taminomara

@taminomara taminomara commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Problem

Context-injection commands (the bang-backtick reads in SKILL.md) run at skill load, and a non-zero exit can abort the skill before its gate logic runs. Multiple skills used commands that exit non-zero when their subject is absent — most visibly the which <tool> availability checks, which exit 1 when the tool isn't installed (the failure seen when an executable under test is missing).

What changed

Tool-availability checks. Every which <tool> context-injection read now uses which <tool> || echo "not installed" (exits 0, injects a clear sentinel). tdd and refactor used the git --version anti-pattern for their "git installed" check (exit 127 when git is absent); both now use the guarded which form.

Git ref / repo / PR state. Guarded the commands that exit non-zero when a precondition is absent, each with 2>/dev/null || echo <sentinel>, and updated every consumer that gated on an empty value to also read the sentinel:

  • git symbolic-ref --short refs/remotes/origin/HEAD and git log/diff origin/HEAD...HEAD — exit 128 when origin/HEAD is unset
  • git branch --show-current / git status --porcelain — exit 128 outside a repo
  • gh pr diff --name-only — fails with no PR
  • git config user.name/email — exit 1 when the identity is unset

han-release. Standardized its prerequisite sentinel from MISSING to not installed.

Guidance. The plugin-builder guidance wrongly claimed which has "no error exit code" on not-found and listed || echo as a pattern to avoid. Corrected to teach the guarded form and carve out the exception for any command that exits non-zero when its subject is absent.

Evidence

  • which <missing> → exit 1; which <missing> || echo "not installed" → exit 0.
  • git symbolic-ref --short refs/remotes/origin/HEAD with no origin/HEAD → exit 128; git log origin/HEAD..HEAD → exit 128.

Known residuals (intentionally not changed)

Three han-release reads of the han repo's own guaranteed-present files (grep … CHANGELOG.md, jq … marketplace.json ×2) are left as-is: a word-sentinel would corrupt their parsed output, and the clean fix is a file-existence prerequisite gate — out of scope for this PR.

🤖 Generated with Claude Code

@taminomara taminomara force-pushed the fix/which-availability-guards branch from 98fb02d to 9b856fc Compare July 2, 2026 21:09
… can't abort a skill

Context-injection commands (the bang-backtick reads in SKILL.md) run at skill
load, and a non-zero exit can abort the skill before its gate logic runs.
Several skills used commands that exit non-zero when their subject is absent:

- `which <tool>` exits non-zero (and prints a verbose not-found line to stderr
  on some builds) when the tool is not installed — the reported failure when an
  executable under test is missing. Now `which <tool> 2>/dev/null || echo "not
  installed"` across every skill that probed git/gh/jq, plus the two "git
  installed" checks in tdd and refactor that used the `git --version`
  anti-pattern the guidance itself warns against (exit 127 when git is absent).

- `git symbolic-ref --short refs/remotes/origin/HEAD` exits 128 when
  `origin/HEAD` is unset; `git log/diff origin/HEAD...HEAD` likewise; `git
  branch --show-current` / `git status --porcelain` exit 128 outside a repo;
  `gh pr diff --name-only` fails with no PR; `git config user.name/email` exit
  1 when the identity is unset. Each is now guarded with `2>/dev/null || echo
  <sentinel>`, and each consumer that gated on an empty value now also reads
  the sentinel.

- Standardized han-release's prerequisite sentinel from MISSING to
  "not installed".

Also corrected the plugin-builder guidance, which wrongly claimed `which` has
"no error exit code" on not-found and listed `|| echo` as a pattern to avoid;
it now teaches the guarded `which ... 2>/dev/null || echo "not installed"` form
and no longer treats `|| echo` as an anti-pattern.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@taminomara taminomara force-pushed the fix/which-availability-guards branch from 9b856fc to 613811b Compare July 2, 2026 21:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant