Skip to content

docs(source-control): give the nesting invariant one owner, an expiry, and a recorded fixture - #2349

Merged
kyle-sexton merged 5 commits into
mainfrom
fix/2213-nesting-invariant-ssot
Aug 12, 2026
Merged

docs(source-control): give the nesting invariant one owner, an expiry, and a recorded fixture#2349
kyle-sexton merged 5 commits into
mainfrom
fix/2213-nesting-invariant-ssot

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Summary

skills/worktree/SKILL.md carries the claim that justifies a machine-wide worktree-placement rule enforced by a fail-closed hook. Four defects sat in it.

#2213 — the claim was restated as an undated absolute at 13 sites

Against exactly two dated statements. Worse, the one site asserting freshness — "It is the live constraint, not a historical one" — was itself undated, so a pointer landed the reader precisely there.

The fix is not thirteen updated copies. SKILL.md now carries the claim under an explicit ### The nesting invariant, verified heading and every other surface points at it. Sites converted: SKILL.md:54/:106, .claude-plugin/plugin.json, README.md (regenerated from the manifest), context/create.md ×3, scripts/worktree-create.sh ×5, hooks/worktree-create-gate.sh.

A new test, skills/worktree/nesting-invariant-ssot.test.sh, is what stops the re-drift: it fails when a second site states the mechanism, so the next person to explain it in place has to point instead.

Two deliberate deviations, stated so neither reads as an oversight:

  1. Pointers do not restate the as-of date, which the issue's fix sketch asks for. Twelve restated dates are twelve drift sites — the defect being removed. Pointers instead say the claim is dated and measured, and name the section carrying the stamp. One stamp, one place to refresh.
  2. The two exit-3 heredocs keep a short restatement alongside their pointer. They are read at the moment creation fails, when the reader cannot go follow a link; a pointer-only refusal there would be a regression. Both restatements are deliberately non-causal ("can pick up") rather than the absolute the sweep removed.

#2213 — both recheck triggers were structurally incapable of firing

The claim was guarded by "a release note naming worktree rule-file loading" or "upstream #16600 changing state". Neither can fire: #16600 has not changed state since well before the 2026-08-07 as-of date, and an event-keyed trigger cannot fire on an opaque stanza (2.1.226 is entirely "Bug fixes and reliability improvements"). The most consequential claim in this plugin was guarded by two triggers that could not go off.

The stamp now adds an unconditional expiry — 2.1.244 or 2026-11-07, whichever comes first — composed with docs/conventions/upstream-drift/ rather than inventing a parallel mechanism, and states why the event triggers cannot carry it alone so a later reader does not delete the expiry as redundant. The test pins that reasoning in place.

#2213 — the ownership claim was a false absolute with no back-channel

"This skill is the canonical owner … — no external prose doc" was untrue: a consumer doc outside this repository defers mechanism to this skill and is more current than it. Ownership is now scoped to this plugin fleet, and states how a consumer who measures a contradiction gets that correction back into the owner. Per the issue's scope correction, the consumer is a machine-local file outside this repository and is not touched.

#2212 — the measurement was disputed, read as settled, and had no fixture

The 2.1.224 leak measurement was disputed, not refuted (a 2.1.227 counter-reproduction did not observe it), and neither run recorded its fixture — so the two results could not be compared and the claim was not adjudicable, while reading as settled.

Now: the section names the dispute, and carries an arm-by-arm status table so a fix to one arm cannot silently weaken another. Per the issue's explicit instruction, the nested-in-an-unrelated-repo arm is recorded as untested by anyone and NOT refuted — the dispute does not reach it.

fixtures/nesting-invariant-probe.sh pins every discriminator neither original run disclosed: creation mechanism, launch mode, the exact paths: glob and its anchoring root, whether the parent's rule file was committed, and the three placements as separate arms. It also guards the trap that most likely produced the dispute: zero trace events means the hook did not fire — a fixture failure, not evidence of absence — and the script says so rather than printing a null.

The probe is written and has NOT been run. That is stated at the top of the script, in fixtures/README.md, and here. Nothing in this PR is claimed on its authority. It converts a recheck trigger into a recheck procedure; running it is what would settle the arm.

#2212 — the reproduction guidance contradicted the hooks docs

It claimed the single-string command shape "silently never fires". https://code.claude.com/docs/en/hooks (raw markdown, fetched 2026-08-11) documents both command forms with no event-specific carve-out, and the documented rule is narrower: "Set args whenever the hook references a path placeholder, since each element is passed as one argument with no quoting." This plugin's own hooks/hooks.json registers all three of its hooks in the single-string form and they fire. Replaced with the documented rule; the genuinely unknown part is now named as unknown — whether the single-string form fires for an InstructionsLoaded hook supplied via claude -p --settings <file> is unprobed by anyone.

#2212 — the version basis read as a release fact

"which 2.1.224 already handles correctly" sat several sentences from the only "Basis:" clause and had already been misread as a version fact by two independent readers. The basis is now inlined at the claim: it is a null result from the same trace, not a release note, and the changelog scan behind it is packet-sourced and has not been re-run — said so explicitly.

Security review: no trust surface changes. No new hook, grant, or network read. The one new executable is an opt-in probe run by hand; it writes only under a mktemp workdir and creates throwaway repos. The .claude-plugin/plugin.json edit is a description string only — no new userConfig key, no default changed.

Test plan

New test, on this branch:

$ bash plugins/source-control/skills/worktree/nesting-invariant-ssot.test.sh
PASS: [1] the undated absolute form of the claim appears nowhere
PASS: [2] the measurement is stated at exactly one site
PASS: [3] that site is the owner
PASS: [4] the owner carries the anchor every pointer cites
PASS: [5] the owner carries an as-of date
PASS: [6] the owner carries an unconditional expiry, not only event triggers
PASS: [7] the expiry states why the event triggers cannot carry this alone
PASS: [8] the owner names the arm as disputed rather than settled
PASS: [9] the owner points at the fixture that would adjudicate it
PASS: [10] the former restatement sites cite the owner by name (7 files)
# rc 0

Fails before the change — the same test against the pre-change docs (stashed doc edits):

FAIL: [1] the undated absolute form of the claim appears nowhere — expected 0 got 6
      sites: .claude-plugin/plugin.json README.md hooks/worktree-create-gate.sh
             scripts/worktree-create.sh skills/worktree/SKILL.md skills/worktree/context/create.md
FAIL: [4] the owner carries the anchor every pointer cites — expected "### The nesting invariant, verified"

No regression in the neighbouring suite (0.52.0's gate work is now on main):

$ bash plugins/source-control/hooks/worktree-create-gate.test.sh
rc=0  pass=34  fail=0

Repo gates, run locally on the rebased branch:

$ python3 scripts/sync-plugin-options-docs.py --check
  plugin options docs: up to date
$ bash scripts/check-changelog-parity.sh --check          # rc 0
$ bash scripts/check-changelog-parity.sh --check-order
  All 76 changelog(s) read newest-first with no duplicate versions.
$ bash scripts/check-changelog-parity.sh --check-bump origin/main
  Every plugin whose version changed vs origin/main has a '## [<version>]' CHANGELOG.md entry.
$ npx markdownlint-cli2 "**/*.md"
  Linting: 1095 files
  Summary: 0 issues in 0 files
$ shellcheck -x -f gcc <both new shell files>             # rc 0, no findings
$ bash scripts/check-shell-portability.sh --paths <both new shell files>
  No unexcused GNU-only constructs in 2 shell file(s).
$ git grep -nE '^(<<<<<<< |>>>>>>> |\|\|\|\|\|\|\| )' -- plugins/ scripts/ docs/
  (no output — checked before pushing, per the conflicted-merge-ref hazard below)

Version basis re-derived, not remembered. main at 4c90b454 carries source-control 0.52.0; this change adds behavior-adjacent doc surface and a new test, so 0.53.0. The 0.53.0 on the pre-rebase branch was computed against a superseded rebase and was re-checked against main, not carried forward on trust.

Related

Closes #2213

#2212 was closed by #2332 while this branch was in flight, covering only its D-F1 and D-F6 rows — that PR says so itself ("D-F2 (fixture recording) remains open"). This PR carries D-F2, the row it left open, plus the same two corrections restated inside the restructured owner section. Both of #2332's corrections were verified present here before the rebase resolution was taken:

$ grep -c "null result from this same trace" …/SKILL.md                          # 1  (D-F1)
$ grep -c "Set \`args\` whenever the hook references a path placeholder" …/SKILL.md  # 1  (D-F6)
$ grep -n "silently never fires" …/SKILL.md
75: … An earlier version of this section claimed … "silently never fires"; that is not what the docs say
    # the falsified claim survives only inside its own refutation

The 0.53.0 CHANGELOG entry records the supersession so the two releases do not read as independent fixes of the same rows.

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".

Adjacent, not touched: #1971 (whether to file an upstream issue for nested-worktree rule inheritance — same claim, different question; this PR makes its status legible), #2208 (upstream draft), #2309 (merged — 0.52.0, the gate work these pointers sit alongside).

Reviewer note, worth verifying rather than trusting: this branch was rebased onto a fast-moving main. A conflicted merge ref suppresses the pull_request-triggered workflow runs entirely, so a PR can display 3 checks, all passing instead of ~35. Compare the check count against a known-good PR before reading this as green — "0 failures" is not sufficient on its own.

@cursor

cursor Bot commented Aug 12, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@claude

claude Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 1m 21s —— View job


Security review complete

  • Gather context (PR diff, changed files)
  • Review shell scripts for injection / unsafe handling
  • Review docs/config changes for permission or trust-boundary shifts
  • Post findings

Scope: all 10 files changed in this PR (git show e9604c7, the PR's single commit against origin/main at be26bd1).

Findings

No security issues found.

What I checked, and why it's clean:

  • hooks/worktree-create-gate.sh, scripts/worktree-create.sh — every changed line in both files is a comment or heredoc text (the exit-3 refusal messages printed to stderr). No executable logic, quoting, variable expansion, or control flow changed. The containment guard, path-refusal logic, and exit codes are byte-identical to what's on main.
  • fixtures/nesting-invariant-probe.sh (new, 165 lines) — opt-in, run-by-hand only; not wired into any hook, CI job, or automated trust path. It operates entirely inside a mktemp workdir, creates throwaway local git repos, and drives claude -p against fixed, hardcoded strings (src/target.md, literal prompt text) — nothing here is attacker-influenced. The generated hook script and trace-settings.json are built with printf %s against paths derived from mktemp, not external input, so there's no injection surface. No secrets, no network calls beyond the local claude CLI invocation, no privilege changes.
  • nesting-invariant-ssot.test.sh (new) — read-only git grep assertions over repo text. No external input, no shell injection surface.
  • .claude-plugin/plugin.json, README.md, SKILL.md, context/create.md, fixtures/README.md, CHANGELOG.md — prose/doc edits and a version bump (0.52.0 → 0.53.0) only. No permissions:, hook registration, userConfig keys, or default values changed — confirmed the plugin.json diff touches only two description strings.
  • No GitHub Actions workflow files are in this changeset, so no pull_request_target/workflow_run/permissions-widening surface applies.
  • No credentials, tokens, or secret handling anywhere in the diff.

This PR's stated self-review ("no trust surface changes... The one new executable is an opt-in probe run by hand... The .claude-plugin/plugin.json edit is a description string only") checks out against the actual diff.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e9604c7684

ℹ️ 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".

…, and a recorded fixture

The mechanism claim justifying a machine-wide worktree-placement rule enforced
by a fail-closed hook was restated as an undated absolute at 13 sites against
exactly two dated statements — and the one site that asserted freshness ("It is
the live constraint, not a historical one") was itself undated, so a pointer
landed the reader precisely there. The fix is not thirteen updated copies. It is
one owner, twelve pointers, and a test that fails when a second site states the
mechanism.

Three further defects in the same paragraph:

- Both recheck triggers were structurally incapable of firing. #16600 has not
  changed state since well before the as-of date, and an event-keyed trigger
  cannot fire on an opaque release stanza. The stamp gains an unconditional
  expiry (2.1.244 or 2026-11-07), composed with docs/conventions/upstream-drift
  rather than a parallel mechanism.
- The measurement was disputed, not refuted, and neither the original run nor
  its counter-reproduction recorded a fixture — so it was unadjudicable while
  reading as settled. It now names the dispute, carries an arm-by-arm status
  table (the nested-in-an-unrelated-repo arm is untested and NOT refuted), and
  ships a probe pinning every discriminator neither run disclosed. The probe is
  written and has NOT been run; that is stated at the top of the script and in
  the fixture record, and nothing is claimed on its authority.
- "the single-string form silently never fires" contradicts the hooks docs,
  which document both command forms with no event-specific carve-out — and this
  plugin's own hooks.json uses the single-string form throughout. Replaced with
  the documented args rule; the genuinely unprobed question is named as unknown.

The "no external prose doc" absolute was false and is now scoped, with an
inbound channel so a consumer that measures a contradiction can correct the
owner instead of diverging from it silently.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
kyle-sexton and others added 4 commits August 12, 2026 05:03
…2333)

Fixes #2162.

Adds `scripts/test-git-helpers.sh` with `git_init_safe` (refuses paths
inside the current checkout) and `git_test_config` (per-invocation
identity). Adopted across the six harnesses named in the issue.

## Related

Fixes #2162. Shared git test harness adoption across plugin test
scripts.

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
…ption change

Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
… with SSOT pointer

Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
… D-F1/D-F6 fix

#2332 shipped the same two corrections inside the old single-paragraph shape
while this release restructures that paragraph into an owned section. Both of
its corrections survive here; what 0.53.0 adds is D-F2, the fixture #2332
explicitly left open. Saying so keeps the two entries from reading as
independent fixes of the same rows.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@claude

claude Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 1m 55s —— View job


Security review complete

  • Gather context (PR diff, changed files)
  • Review shell scripts for injection / unsafe handling
  • Review docs/config changes for permission or trust-boundary shifts
  • Post findings

Scope: all 16 files changed in this PR (gh pr diff 2349, HEAD 312d7a6).

Findings

No security issues found.

What was checked

  • plugins/source-control/hooks/worktree-create-gate.sh, plugins/source-control/scripts/worktree-create.sh — every changed line is a comment or heredoc refusal-message rewrite (pointing at the new SKILL.md § "The nesting invariant, verified" instead of restating the mechanism). No executable logic, quoting, variable expansion, or control flow changed; the containment guard and exit codes are unchanged. No variables are interpolated into the touched heredoc bodies, so there's no new injection surface there.
  • plugins/source-control/skills/worktree/fixtures/nesting-invariant-probe.sh (new, opt-in, run by hand — not wired into any hook, hooks/hooks.json, or CI workflow):
    • The generated InstructionsLoaded trace hook and trace-settings.json are built with printf %s/printf %%s against $TRACE_DIR/$HOOK, which derive from mktemp -d (or an operator-supplied $1 workdir) — not from data that flows through the claude -p session or any external/attacker-controlled input. The -p prompt and read paths (src/target.md, "OK") are fixed literals.
    • No secrets, no network calls beyond the local authenticated claude CLI, no privilege or permission changes. It only creates throwaway local git repos under its own workdir.
    • The two P1 comments from the Codex review (missing positive control before accepting a null trace; wrong enclosing-repo path in the unrelated-nested arm) are fixture-correctness bugs — they'd make the probe's measurement less trustworthy, not create a vulnerability — so per this review's scope they're out of bounds here (no security impact: the script still only touches its own mktemp sandbox either way).
  • plugins/source-control/skills/worktree/nesting-invariant-ssot.test.sh (new) — read-only git grep -lIF assertions over repo text with static, hardcoded patterns. No external input, no injection surface.
  • scripts/test-git-helpers.sh (new) and its callers (affected-tests.test.sh, check-changed-skills.test.sh, check-changelog-parity.test.sh, check-docs-only.test.sh, fleet-state.test.sh) — these tighten a trust boundary rather than loosen one: git_test_config moves identity/signing config from persistent git config calls to per-invocation -c flags, and git_init_safe explicitly refuses to git init inside the current checkout tree. This is a safety improvement (stops test scaffolding from writing throwaway identity into a real repo's .git/config), consistent with the prior fix in fix(tests): stop git_init from persisting identity into real repos #2333.
  • plugins/source-control/.claude-plugin/plugin.json, README.md, SKILL.md, context/create.md, fixtures/README.md, CHANGELOG.md — prose/doc restructuring and a version bump (0.52.1 → 0.53.0) only. Confirmed the plugin.json diff touches only two description strings — no permissions:, hook registration, userConfig key, or default value changed.
  • No GitHub Actions workflow files are in this changeset, so no pull_request_target/workflow_run/permissions-widening surface applies (per instructions, unpinned-action/permissions concerns are zizmor's lane in any case).
  • No credentials, tokens, or secret handling anywhere in the diff.

This PR's own stated self-review ("no trust surface changes... The .claude-plugin/plugin.json edit is a description string only") checks out against the actual diff, and the new test-helper changes are a net trust-boundary improvement rather than a regression.
· branch fix/2213-nesting-invariant-ssot

@kyle-sexton
kyle-sexton merged commit 534ca43 into main Aug 12, 2026
37 checks passed
@kyle-sexton
kyle-sexton deleted the fix/2213-nesting-invariant-ssot branch August 12, 2026 05:23
@claude

claude Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 7m 17s —— View job


Code review

  • Gather context (diff, CLAUDE.md, changed files)
  • Review docs/SSOT changes (SKILL.md, plugin.json, README.md, context/create.md)
  • Review shell script changes (worktree-create-gate.sh, worktree-create.sh) — non-security aspects
  • Review new test (nesting-invariant-ssot.test.sh)
  • Review new fixture probe (nesting-invariant-probe.sh) — correctness only, security handled by dedicated lane
  • Review unrelated-looking files (scripts/*.test.sh, fleet-state.test.sh, test-git-helpers.sh)
  • Post findings

Scope: all 16 files changed in this PR (gh pr diff 2349, HEAD 312d7a6). Security aspects (injection, trust-surface, permissions) are out of scope for this lane per REVIEW.md's split — see the separate security-review comment on this PR.

Posted 3 inline comments on lines I could anchor to. One more, cross-file, below.

Findings

1. fixtures/nesting-invariant-probe.sh arm D is checking the wrong repo and can't match its own read target (L161, inline comment posted) — the unrelated-nested arm's verdict check greps for the worktree's own origin repo instead of the physically-enclosing unrelated repo, and even fixed, the glob it writes can't match the read path's location. This is the one arm SKILL.md names as "untested by anyone" and the fixture is supposed to be the adjudicator for; as written it would misreport a clean "no" rather than surface the defect.

2. Same file, no positive control before trusting a "no" verdict (L122, inline comment posted) — the script guards the zero-trace case explicitly but doesn't confirm the triggering read actually happened (via the worktree's own rule producing a path_glob_match) before accepting an absence of the parent's rule as a real negative.

3. The new SSOT test's coverage is narrower than the invariant it enforces (cross-file — nesting-invariant-ssot.test.sh L38 vs. fixtures/README.md L95-96) — Test 1 does a same-line, case-sensitive git grep -F for the exact phrase "a read matching a path-scoped rule's glob also loads". This PR's own new fixtures/README.md ("Claim under test... a read matching a / path-scoped rule's glob also loads the enclosing checkout's copy of that rule.") restates essentially the same mechanism claim, but the hard-wrapped line break happens to fall inside the matched phrase, so the grep misses it; nesting-invariant-probe.sh's header comment restates it too, using ALSO (uppercase), which also evades the case-sensitive match. Neither instance is actively harmful today — both are immediately qualified by a "not settled" caveat right after, unlike the 13 sites this PR just fixed — but given the entire point of this test is to prevent literal-text restatement drift, a guard whose enforcement depends on word-wrap position and letter case is fragile: a future edit could reflow either file (accidental pass→fail flip) or a genuinely careless restatement could land in either file and slip past silently. Worth normalizing whitespace/case before matching, or scoping the check to only the files the PR intends to be pointers (excluding the fixture docs, which legitimately need to restate "what is under test").

4. Cosmetic (worktree-create.sh L485, inline comment posted) — an unwrapped comment line, minor readability nit only.

What's solid

  • The single-owner/pointer restructuring of the nesting invariant (SKILL.md § "The nesting invariant, verified") is a real architectural improvement over 13 undated copies, and the new SSOT test (modulo finding 3) is a good mechanism for preventing re-drift.
  • scripts/test-git-helpers.sh is a clean extraction — it removes a lot of duplicated, near-identical git-identity boilerplate across 4 test files without changing test semantics; spot-checked several converted call sites and the -c-flag (non-persisting) vs. persisted-git config (git_init_test_repo) split is used consistently with how each caller needs it.
  • Doc cross-references (anchor links, docs/conventions/upstream-drift/README.md relative path from SKILL.md) all resolve correctly.
  • I did not execute the shell scripts/tests myself (no execution tool available in this lane) — the PR's Test plan output is author-claimed and unverified by me; my findings above come from static reading of the fixture logic, not from running it.

# here as refuting arm A: they are different claims.
P4="$(mkparent "$WORKDIR/parentD" "$GLOB")"
UNREL="$(mkparent "$WORKDIR/unrelated" "$GLOB")"
arm unrelated-nested "$P4" "$UNREL/nested/wt" "src/target.md"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Arm D (unrelated-nested) checks the wrong repository, and its glob can't match its own target even if fixed.

arm() is called as arm unrelated-nested "$P4" "$UNREL/nested/wt" "src/target.md", so inside the function repo="$P4" (the worktree's own checkout) while the physically-enclosing, git-unrelated repo is $UNREL. But the verdict check at line 126 is:

if grep -qF "$repo/.claude/rules/scoped.md" "$TRACE_DIR/trace.jsonl"; then

$repo/.claude/rules/scoped.md here is $P4/.claude/rules/scoped.md — a path that isn't even an ancestor of $wt on disk ($P4 = $WORKDIR/parentD, $wt = $WORKDIR/unrelated/nested/wt), so it can never appear in a trace for this arm regardless of whether the leak the arm exists to test for is real. The check that would actually adjudicate this arm is grep -qF "$UNREL/.claude/rules/scoped.md".

Even fixing that, the arm still can't pass: mkparent "$WORKDIR/unrelated" "$GLOB" writes paths: ["src/**"] into $UNREL/.claude/rules/scoped.md, anchored (per the doc's own "anchoring root" framing) at $UNREL. The read target is $wt/src/target.md = $UNREL/nested/wt/src/target.md, whose path relative to $UNREL is nested/wt/src/target.md — not matched by src/**. So arm D as written cannot produce a true positive no matter what the harness actually does.

This is the one arm the PR explicitly flags as "untested by anyone" and the fixture the SKILL.md stamp names as "the adjudicator" for it — as written, running it would misreport this arm as refuted (a clean "no") rather than surfacing a fixture defect, which is exactly the trap fixtures/README.md says the script guards against. Fix this →

printf 'worktree target\n' >"$wt/$readpath"
( cd "$wt" && claude -p "Read $readpath and reply with exactly: OK" \
--settings "$SETTINGS" >/dev/null 2>&1 )
if [[ -s "$TRACE_DIR/trace.jsonl" ]]; then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A nonempty trace isn't proof the triggering read happened — no positive control before reading a "no" verdict.

claude -p ...'s exit status is discarded (line 120-121), and the only gate before trusting a "PARENT rule loaded? no" verdict is [[ -s "$TRACE_DIR/trace.jsonl" ]] — i.e. some InstructionsLoaded event fired. InstructionsLoaded fires at session start regardless of whether the model goes on to actually read $readpath (it may decline, error, or answer without invoking the Read tool), so a nonempty trace can come entirely from session-start instructions loading, with the paths:-glob-triggered read that's the actual subject of the test never having occurred.

The script explicitly guards the zero-trace case as "a fixture failure, not evidence of absence" (lines 131-136), but doesn't extend that same skepticism to a nonzero-but-incomplete trace — it never confirms the worktree's own scoped.md rule produced a path_glob_match (a positive control) before accepting the parent's absence as a real "no". Since this arm's "no" is the one that would settle the disputed measurement in SKILL.md § "The nesting invariant, verified", a false "no" here would silently downgrade a disputed claim to refuted.

# the helper's core purpose — see skills/worktree/SKILL.md § "The nesting
# invariant, verified" for the measured claim, and note that a worktree dropped
# inside a .git or bare directory additionally mixes the checkout into git
# metadata, which is a separate and undisputed reason to refuse. The root resolution above does not catch a root explicitly

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: this line runs on for ~130 columns, folding "...a separate and undisputed reason to refuse." together with the next sentence ("The root resolution above does not catch a root explicitly") that used to start its own wrapped line. Every other line in this comment block wraps at ~80-90 columns — looks like an edit artifact from the rewrite rather than intentional. Not functionally significant (it's a # comment), just a readability nit.

@github-actions

Copy link
Copy Markdown

Claude has reviewed this PR 1 time. The lane skips further automatic reviews after 5; deleting this comment resets the count.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants