fix(source-control): move git out of skill pre-compute so worktree-isolated agents can invoke these skills - #1679
Conversation
…ents can invoke these skills The harness composes an entire `## Pre-computed context` block into ONE shell invocation. The worktree-isolation Bash guard refuses a git-bearing compound command it cannot statically verify, so all five git-bearing skills in this plugin failed at load from a worktree-isolated agent. `worktree` is the sharpest case: the skill for managing worktrees could not be invoked from inside one. Removes the git lines from each pre-compute block and re-acquires those values in the skill body as individual Bash calls, one command per call. Non-git pre-compute lines are left untouched — `commit` keeps its exec-bit and user-global config probes, `babysit-prs` keeps both `gh` lines. `commit`'s two repo-scoped config-layer probes were themselves compound one-liners that re-derived the repository root inline, so they are decomposed rather than moved across intact: resolve the root once, then substitute the literal path into the tracked-layer and personal-overlay checks. The root-anchoring requirement and the `present but UNTRACKED` rule are unchanged. Each gather block states that a failed command means "unknown, carry on", replacing the `2>/dev/null || echo` fallbacks that plain Bash calls do not reproduce. Two reference spokes that described these commands as pre-computed are corrected. `create.md`'s `--pushed` section had justified ignoring the session context partly on `!`-substituted lines not being `git -C`-redirectable, which stopped being true; the instruction to re-resolve from the target worktree is unchanged. `babysit-prs` is held at exactly 499 lines so this does not consume the one line it has left under the 500-line hard cap (#1626). Refs #1619 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 3m 56s —— View job PR Review
Overall: This is a sound, well-scoped fix. The mechanism is correctly identified — the harness composes the entire pre-compute block into one shell invocation, and the worktree-isolation guard refuses it when it contains git — and the remedy (individual body-level Bash calls) is the right one. The PR description is exemplary. A few items worth attention: Finding 1 —
|
| Skill | Old | New |
|---|---|---|
commit |
git diff --cached --stat | tail -1 |
git diff --cached --stat |
commit |
git status --short | head -20 |
git status --short |
resolve-conflicts |
git status | head -4 |
git status |
worktree |
git worktree list | head -30 |
git worktree list |
These pipes were appropriate in pre-compute (context is injected as literal text up front). In the body, the model runs the command as a tool call and can handle richer output — the full stat, all conflicted paths — which is actually more useful. A single-pipeline call like git status --short | head -20 is also not a compound command, so it would not trip the worktree guard if the model includes the pipe itself. Not a defect; just noting it's a deliberate departure from the original truncation behavior.
Everything else looks correct
- Version bump (0.33.1 → 0.33.2): Appropriate for a fix that changes consumer-visible behavior (skill load from isolated agents).
shell: bashretained: Confirmed inert without!lines; keeping it avoids a frontmatter-contract change.commit'stest -f "$HOME/.claude/source-control.md"stays in pre-compute: Correct — it has no git dependency and composes safely.exec-bit-check.sh --probestays in pre-compute: Correct — it anchors itself viagit rev-parseinternally and is a single command the harness can verify.babysit-prsnet-zero on lines: Confirmed — exactly 499 lines.pull-request/reference/create.mdupdate: The revised rationale is accurate. The original claim ("a!-substituted line cannot begit -C-redirected") is no longer true; the new text correctly preserves the instruction while correcting the stated reason.commit/reference/exec-bit.md: The one-word removal ("pre-computed") is accurate and precise.worktree/SKILL.mdmotivation note: "which made the worktree skill itself uninvocable from inside a worktree" is the clearest articulation of the defect's irony — good to have it here.- CHANGELOG entry: Thorough, accurate, correctly scoped.
|
Claude finished @kyle-sexton's task in 2m 3s —— View job Security Review — PR #1679
No exploitable security vulnerabilities found. All nine changed files are markdown skill-instruction files and documentation. No executable scripts, GitHub Actions workflows, credential-handling code, or harness engine code is touched. The analysis below covers the surfaces that warranted explicit inspection. Path substitution in
|
…verification precisely Same two classes the Codex review caught on the sibling PR #1676, applied here before they were reported. The pre-computed lines carried output caps that plain Bash calls do not reproduce: `git status --short | head -20`, `git diff --cached --stat | tail -1`, `git worktree list | head -30`, and `git status | head -4`. Without them a large or deeply-worktreed repository would push its full output into model context. Each cap is restored as a reading rule rather than a pipe — a piped git command is compound, which is exactly the shape this PR removes, so re-adding the pipes would reintroduce the defect in the body. The CHANGELOG now states what was actually observed rather than implying the edited skills were exercised under isolation. They cannot be: skills load from the version-keyed plugin cache, and 0.33.2 does not exist there until this ships. Refs #1619 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 1m 52s —— View job PR Review (sync — post
|
|
Claude finished @kyle-sexton's task in 1m 37s —— View job Security Review — PR #1679
No exploitable security vulnerabilities found. All nine changed files are markdown skill-instruction files and documentation. No executable scripts, GitHub Actions workflows, credential-handling code, or harness engine code is touched. The second commit ( Path substitution in
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8a1c221f48
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…s config-layer probes Review finding on #1679. The decomposed config-layer probes substitute the output of `git rev-parse --show-toplevel` into `git -C <root> …` and `test -f <root>/…`, and the templates were unquoted. A repository root containing a space — routine on Windows, `C:\Users\First Last\…` — word-splits there, so the command either targets the wrong directory or fails, and the skill would report a present config layer as absent. Both templates are now `"<root>"`, with an explicit instruction to always quote the substituted path rather than leaving it to the reader. The unquoted `git -C <worktree>` templates in babysit-prs/reference are pre-existing and untouched here. Refs #1619 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 1m 44s —— View job PR Review (sync — post
|
|
Claude finished @kyle-sexton's task in 2m 1s —— View job Security Review — PR #1679 (commit
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9d35fa4b04
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…xample The hygiene job failed on `machine-specific-paths`, not on EOL drift — the `eol-renormalize` step reported "Index EOL clean — no stale blobs" and the "Drifted files:" text in the log is echoed script source, not output. The failing check greps tracked files for machine-specific absolute paths and flagged the illustrative `C:\Users\First Last\…` this branch added to commit's config-layer step 1. The gate's Windows-user-path pattern excludes `<` from the segment after `Users`, so portable placeholders pass and literal home paths do not. `<first last>` keeps the point the sentence is making — a repository root whose path contains a space, which is why the substituted root must be quoted — while satisfying the gate. Prose only; no behavior change. Refs #1619 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 1m 43s —— View job PR Review (sync — post
|
|
Claude finished @kyle-sexton's task in 2m 42s —— View job Security Review — PR #1679 (commit
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 03457ca34b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ore real output bounds
Three P2 review findings on commit/SKILL.md, all in the repository-context block.
Shell-escaping the substituted repository root: eliminated rather than escaped.
The probes now use git's repo-root-relative magic pathspec `:/` instead of
substituting the output of `git rev-parse --show-toplevel` into a later command,
so a root containing a space, `$(...)`, a backtick, or a double quote can neither
break the command nor inject into it. Double-quoting a substituted root does not
neutralize a command substitution, which is why quoting was the wrong fix.
Verified from a subdirectory: `:/` resolves against the working-tree root
regardless of cwd. The personal overlay drops `test -f "<root>/..."` for the same
pathspec-based existence probe.
Output caps: the read-time prose ("read at most the first 20 entries") bounded
nothing, because the Bash tool returns a command's complete output into context
before there is anything to decide about. The `| head -20` and `| tail -1` pipes
are restored on the body commands, and the same fictional bound is corrected in
`worktree` (`| head -30`) and `resolve-conflicts` (`| head -4`). These are body
Bash calls, not pre-compute; the shape #1619 is about is the harness composing a
whole pre-compute block into one shell invocation.
Team-layer three-state check: one `--error-unmatch` call exits nonzero for both
`present but UNTRACKED` and `absent`, so it cannot establish the documented
three states. A second `git ls-files --cached --others` existence probe separates
them (`--exclude-standard` deliberately omitted so a gitignored file is still
seen), with an explicit ordered combining table. The generic unknown-value rule
is narrowed so it no longer swallows the distinction: a nonzero `--error-unmatch`
exit is a result, and only a probe that could not run at all is unknown.
Refs #1619
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 36s —— View job PR Review (sync — post
|
| Trackedness (1) | Existence (2) | Team-layer state |
|---|---|---|
| exit 0 | not consulted | present (tracked) |
| nonzero | non-empty output | present but UNTRACKED |
| nonzero | empty output | absent |
Crucially, --exclude-standard is deliberately omitted from probes 2 and 3, so a gitignored file is still listed — the test -f approach could not have achieved this. The generic "treat failure as unknown" rule is also correctly narrowed: a nonzero exit from probe 1 is a result, not a failure.
Codex P2 — Shell metacharacter injection: RESOLVED ✅
The :/ magic pathspec eliminates root substitution entirely. A repository root containing $(…), backticks, spaces, or double quotes can neither break the command nor inject into it, because the root never enters the command string. This is the correct fix — as the commit message notes, double-quoting a substituted root does not neutralize command substitution ($() evaluates inside double quotes), so quoting was the wrong approach. The CHANGELOG records this clearly: "double-quoting a substituted root does not neutralize a command substitution, which is why quoting was the wrong fix."
Codex P2 — Output bounds: RESOLVED ✅ (real pipes restored)
commit/SKILL.md L21–22, worktree/SKILL.md, resolve-conflicts/SKILL.md
The | tail -1 and | head -20 pipes are restored as actual bounds on the body Bash calls (not just prose instructions). The commit message explains why: a read-time prose cap bounded nothing, because the Bash tool returns complete output into context before there is anything to decide about. The rationale for why restoring them as body pipes is safe (not the pre-compute composition shape that #1619 is about) is correct and now stated explicitly in each skill.
One honest caveat in the CHANGELOG: whether a pipe in a body call also clears the isolation guard hasn't been verified. Based on the established mechanism (the guard fires on git inside a composed pre-compute block, not on individual body calls), body-level pipes should be fine — but this is the right thing to disclose.
Finding 2 — babysit-prs: maintenance note still inside ## Pre-computed context (OPEN — fifth round)
Still unresolved. The f92e785 commit does not touch babysit-prs/SKILL.md. The gather instruction for branch and working tree remains as a plain-text note inside ## Pre-computed context rather than a dedicated ## Repository context — gather first section matching the other four skills. The note is already two lines; moving it to a new section is line-neutral under the 499-line cap.
Everything else looks correct
:/pathspec rationale in## Repository context: The note at L79–86 explains precisely why:/retires the older two-step and why double-quoting was wrong. This is the right level of explanation for a model-facing instruction — the "why" prevents future regressions.- CHANGELOG revision: The updated entry correctly records all three changes, including the honest caveat about body-pipe isolation verification. The "real bound beats a fictional one" line is accurate.
worktree/SKILL.mdandresolve-conflicts/SKILL.md: Both now have the pipe restored and carry the same "pipe is the bound" rationale paragraph. Consistent withcommit/SKILL.md.- Core fix (worktree-isolation): Unaffected by
f92e785. All five skills remain correct for the primary defect. - Version bump (0.33.1 → 0.33.2): Appropriate.
Summary: f92e785 is a thorough and well-reasoned resolution of all three P2 findings. Finding 1 (team-layer three-state check) is resolved with a superior approach. The only item still open is Finding 2 — the babysit-prs gather note placement — which is low-severity and non-blocking for the core fix.
|
Claude finished @kyle-sexton's task in 2m 5s —— View job Security Review — PR #1679 (commit
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f92e785007
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Correction to this PR's own framing — read before mergingThis PR body states the mechanism as "the harness composes an entire pre-compute block into ONE shell invocation, and the guard refuses a git-bearing compound command." That is wrong, and it was my error, not a reviewer's. An adversarial re-probe, plus a confirmation run from inside an
Decisive controls: Full evidence: #1619 correction. What this means for mergingThe changes here are still worth landing. Moving git into individual body calls is sound, the But this PR does not fully fix Under the corrected rule, That remainder is tracked in #1687, which also notes the hard part: Merging on the strength of what the diff actually improves, with the residue tracked rather than papered over. The CHANGELOG's mechanism wording carries the same inherited error and should be corrected in the #1687 PR alongside the real fix. |
Correcting my own correction — the
|
Moves git out of the
## Pre-computed contextblock in all five git-bearingsource-controlskills, so they can be invoked from a worktree-isolated agent. Sibling of #1676 (
session-flow).No linked issue — this is a partial remediation of #1619 (12 of 43 affected skills, together with
#1676), so it must not auto-close it. #1661 tracks the remaining 31 skills across 16 plugins.
The defect
The harness composes an entire pre-compute block into one shell invocation, and the
worktree-isolation Bash guard refuses that composed command when it contains git and is too complex
to statically verify:
So
commit,pull-request,worktree,resolve-conflicts, andbabysit-prsall failed atload from an isolated agent.
worktreeis the sharpest case — the skill for managing worktreescould not be invoked from inside one.
Mechanism confirmed by direct probe (full write-up on #1619):
code-tidying:batch-simplify(onegit pre-compute line, compound) loads;
knowledge:course-digest(four complex non-git lines)loads;
session-flow:find-handoff(four lines, one pipe-free git line) is refused. Thetrigger is git inside a composed block, not per-line complexity.
The change
Delete the git lines from each pre-compute block; re-acquire those values in the skill body as
individual Bash calls, one command per call. Non-git pre-compute lines are untouched —
commitkeeps its exec-bit and user-global config probes,
babysit-prskeeps bothghlines.Three things worth review attention:
commit's config-layer probes were themselves compound one-liners that re-derived therepository root inline (
R="$(git rev-parse --show-toplevel)" && git -C "$R" ls-files …). Movingthem across intact would have reproduced the defect as a body call, so they are decomposed:
resolve the root once with
git rev-parse --show-toplevel, then substitute the literal path intothe tracked-layer
ls-filescheck and the personal-overlaytest -f. The root-anchoringrequirement and the
present but UNTRACKEDrule are unchanged.2>/dev/null || echo "unknown"; plain Bash calls donot reproduce that, so each gather block now says explicitly that a failed command means
"unknown, carry on" rather than surfacing a raw error.
commit/reference/exec-bit.mdno longercalls the config-layer probes pre-computed.
pull-request/reference/create.md's--pushedsection justified ignoring the session context partly on the grounds that a
!-substituted linecannot be
git -C-redirected — no longer true once these are ordinary Bash calls. The sectionkeeps its instruction (re-resolve explicitly from the target worktree) on the reason that still
holds (session cwd is the wrong branch for an out-of-tree orchestrator).
shell: bashstays on every skill, including the three that now have no!lines at all — inertwithout pre-compute lines, and removing it would be a frontmatter-contract change with no
behavioral benefit.
babysit-prsis held at exactly 499 lines (net-zero), so this does not consume the single lineit has left under the 500-line hard cap (#1626).
Known pre-existing CI failure — not introduced here
babysit-prs/scripts/engine.test.shfails on cleanmainat9f73fc2e, before any change inthis PR:
mainstays green becausecheck-changed-skillsonly runs a skill's script tests when that skillchanges, and
babysit-prshad not changed. This PR touches it, so the gate runs and surfaces thelatent failure. Verified pre-existing by running the test on an unmodified
maincheckout — this PRchanges only SKILL.md prose and touches no engine code. Filed as #1678; not fixed here, since
folding an unrelated guard-test repair into a documentation-shaped migration would obscure both.
Verification
rev-parse --show-toplevel,branch --show-current,status --porcelain,log --oneline -5) each succeed as ordinary Bash calls from inside aworktree-isolated agent — that is what the replacement bodies rely on.
Every skill here is now exactly that shape.
isolated agent. Skills load from the version-keyed plugin cache
(
~/.claude/plugins/cache/<marketplace>/<plugin>/<version>/), so0.33.2does not exist thereuntil this merges and plugins are updated. Post-merge confirmation: update plugins, then invoke
/source-control:worktreefrom an isolated agent, withknowledge:course-digestas the positivecontrol.
Local gates against
origin/main:check-changelog-parity --check-bumppass,check-skill-portabilitypass,markdownlint-cli20 issues,check-changed-skills4 of 5 pass withthe fifth failing only on the pre-existing
engine.test.shabove.Fresh-docs
https://code.claude.com/docs/en/skillsfetched this session (2026-07-26): the page documents"Each
!`<command>`executes immediately (before Claude sees anything)" and says nothing aboutcomposing the block into a single invocation — the observed behavior contradicts it. It also
confirms
shell:acceptsbash(default) orpowershell, which is why leavingshell: bashisinert.
Related
session-flowhalf of this remediation.engine.test.shfailure this PR surfaces but does not cause or fix.babysit-prsline-cap headroom this PR deliberately does not consume.