Skip to content

fix(source-control): the worktree skill's nesting-invariant measurement records no fixture, misattributes its version basis, and ships contradicted reproduction guidance #2212

Description

@kyle-sexton

skills/worktree/SKILL.md carries the measurement that justifies a machine-wide worktree-placement rule enforced by a fail-closed hook. Three defects sit in the two sentences that carry it: the version basis is attached to the wrong clause and has already been misread by two independent readers, the measurement records no fixture so nobody can adjudicate the counter-reproduction that disputes it, and the reproduction guidance contains a parenthetical that the current hooks docs contradict. A reader following that guidance will misdiagnose a null result — which is exactly the error that produced the disputed counter-reproduction in the first place.

Rows

  • I7 D-F1plugins/source-control/skills/worktree/SKILL.md:58 — "which 2.1.224 already handles correctly" reads as a version-fact but is a null result from the same trace; the "Basis:" clause sits several sentences earlier and attaches to the control-arm descriptions.
  • I7 D-F2plugins/source-control/skills/worktree/SKILL.md:56, :58 — the central leak arm is disputed, not refuted, and neither the measurement nor its counter-reproduction records the fixture, so it is not adjudicable.
  • I7 D-F6plugins/source-control/skills/worktree/SKILL.md:58 — "the single-string form silently never fires" is wrong as written; the hooks page documents both command forms with no event-specific carve-out.

Evidence

D-F1 — the version basis attaches to the wrong clause

plugins/source-control/skills/worktree/SKILL.md:58, unchanged at HEAD:

"Note that #16600 concerns memory files, which 2.1.224 already handles correctly — the surface still leaking is path-scoped rules, which no open upstream issue covers."

The only "Basis:" clause in that paragraph precedes the reproduction guidance, several sentences earlier. The reader-error is independently corroborated: a separate item's own "Loose ends" section makes exactly the same drift — "source-control@0.51.6 skills/worktree/SKILL.md:58 states memory files are handled correctly 'as of 2.1.224'. The 2.1.224 changelog stanza carries no memory/worktree/rule line." That is a second careful reader making the version-fact reading, in a different item. The claim is probably true and is not refuted; the defect is attribution hygiene on a surface whose entire value is attribution hygiene. Fix by inlining the basis at the claim ("measured in the same trace — no release note covers this"). LOW.

The changelog scan behind the null result (2.1.224's stanza has 32 entries and no memory/worktree/rule/instruction-loading line) is packet-sourced and was not re-run during verification.

D-F2 — the leak arm is disputed, and the fixture is missing

SKILL.md:56 states the measurement — "measured on 2.1.224: … emits one path_glob_match naming the parent checkout's rule file". :58 states the placement-equivalence arm — "The leak is not specific to .claude/worktrees/: a worktree at a plain non-dot subdirectory leaks identically". The method note discloses hook shape and claude -p --settings <file> but not how the worktree was created or entered (claude -w / EnterWorktree versus a plain cd into a git worktree add directory). No fixture script exists under skills/worktree/ or scripts/.

Arm-by-arm status — carry this decomposition into any fix; do not weaken arms the dispute does not reach:

Arm Status
"leak exists at all on 2.1.224" unreproduced on 2.1.227, undisclosed fixture
"not specific to .claude/worktrees/" same status; the dot-vs-plain half is separately supported by the v2.1.69 changelog wording
"nested in an unrelated repo is worse" untested by anyone, not refuted — do not weaken
"lazy not eager" untested

A self-correction from the audit that must survive: the counter-reproduction's session_start null was initially read as independent corroboration of the skill's suppression arm, and that was retracted — the null is equally consistent with an ordinary ancestor walk stopping at the worktree's own git root because the session was never recognized as a worktree session.

The doc-silence is confirmed and is load-bearing. https://code.claude.com/docs/en/worktrees, fetched 2026-08-11, documents the default .claude/worktrees/<name>/ placement, the three isolation checks, the non-suppressible EnterWorktree approval outside .claude/worktrees/, and what worktrees share with the main checkout (.git, project-scope plugins, permission approvals) — and says nothing about whether a nested worktree's session discovers the parent checkout's .claude/rules/. The claim is doc-unaddressed, not doc-contradicted, so measurement is the only adjudicator, which is what makes the undisclosed fixture fatal. The same page confirms the discriminator matters: "While a session is isolated in a worktree, whether you started it with --worktree, Claude entered one with EnterWorktree, or you resumed a worktree session…" — the harness's worktree-aware behavior attaches to a recognized worktree session, and a plain cd into a git worktree add directory is not obviously one.

Highest-leverage change in this cluster: downgrade the modality at :56/:58 to name the dispute, and record the fixture — creation mechanism, launch mode, exact paths: glob and its anchoring root, and whether the parent's rule file was committed — shipped as a script so a recheck is one command. That converts a recheck trigger into a recheck procedure.

Adjacent: #1971 (OPEN, "worktree: path-scoped rule inheritance in nested worktrees has no open upstream issue — decide whether to file one") — same claim, different question. Link them.

D-F6 — the reproduction parenthetical is contradicted by the docs

SKILL.md:58, inside the reproduction guidance:

reproduce by registering the hook with the `args`-array command shape ({"type":"command","command":"<bash>","args":["<script>"]} — the single-string form silently never fires) and passing it via `claude -p --settings <file>`

https://code.claude.com/docs/en/hooks, fetched 2026-08-11 — both forms documented, no event-specific carve-out. Exec form (args present): "Claude Code resolves command as an executable on PATH and spawns it directly with args as the argument vector. There is no shell…" Shell form (args absent): "The command string is passed to a shell: sh -c on macOS and Linux, Git Bash on Windows, or PowerShell when Git Bash isn't installed." The documented rule is narrower than the skill's: "Set args whenever the hook references a path placeholder, since each element is passed as one argument with no quoting." Corroborated at https://code.claude.com/docs/en/plugins-reference, fetched 2026-08-11: "In shell-form hooks and monitor commands, wrap the variables in double quotes."

This plugin's own hooks/hooks.json registers all three of its hooks in the single-string form — working counter-examples in the same repository. But note the narrowing that governs: the plugin-evidence leg corroborates rather than disposes, because the skill's claim concerns InstructionsLoaded supplied via --settings, a different event and channel. Add to the unverified list: whether the single-string form fires for an InstructionsLoaded hook supplied via claude -p --settings <file> — not probed by anyone. The likeliest true cause is a fixture-specific quoting/substitution failure generalized into a universal claim; the plugin already knows this failure mode from the other direction (CHANGELOG 0.46.x on CLAUDE_PLUGIN_OPTION_WORKTREE_ROOT versus ${user_config.worktree_root}, "which Claude Code rejects in shell-running fields").

Fix: replace the parenthetical with the documented constraint, cite the hooks page with a fetch date (as SKILL.md:34 already does for the worktrees page), and if the single-string failure was genuinely observed, record it as a fixture-specific observation with the exact string that failed.

Provenance

Severity: MED (cluster highest; D-F1 is LOW) · Provenance: AUDITOR_VERIFIED. Unconfirmed legs are named inline: the 2.1.224 changelog scan is packet-sourced and not re-run; whether the single-string form fires for InstructionsLoaded via --settings is unprobed by anyone.
Origin: handoff-inbox item 2026-08-10-plugin-quality-audit-four-components (packet nonce 20260811T023241Z)
Ledger: .work/handoff-inbox-batch-4/ledgers/I7-four-components-023241Z.md § "Lane D — source-control:worktree"
Verified against repo HEAD 685dd381. Audited at 0.51.6; anchors re-located at HEAD (0.51.7) by heading + quoted phrase.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions