Skip to content

fix(worktrees/docs): the subtractions that could never happen - #1832

Open
njrini99-code wants to merge 5 commits into
mainfrom
agent/docs-guard-truth
Open

fix(worktrees/docs): the subtractions that could never happen#1832
njrini99-code wants to merge 5 commits into
mainfrom
agent/docs-guard-truth

Conversation

@njrini99-code

Copy link
Copy Markdown
Owner

The cleanup tool has a guard whose entire purpose is to refuse to present a report built on failed evidence. AGENTS.md promised it. It exists in the code. It did not fire, and the reason is one character of logic.

Measured

Instrumented at the guard's own line, inside the Bash sandbox:

total rows 72   ->   { FAILED: 69, OK: 3 }

96% of PR lookups failed. The condition was failed === all, so the guard stayed silent, the tool exited 0, and the summary read 0 branches deletable — indistinguishable from a genuinely clean repository. That is the exact failure the guard was written to prevent, surviving inside the guard meant to prevent it.

Why it is never unanimous: macOS caches TLS trust decisions, so the first few gh calls succeed from cache before it is exhausted. A partial blackout is the normal shape of this failure; a total one is the special case. A threshold set at the special case is a gate that cannot fire.

Fix

Any failed lookup is now reported on its own line (a row whose lookup failed proves nothing about that branch), and a failure ratio ≥ 0.5 exits 2.

Verified both directions, which is the whole point:

run lookups result
sandboxed 70/73 failed (96%) INFRASTRUCTURE_FAILURE, exit 2
unsandboxed 0 failed no marker, exit 0, normal report

Diagnosis corrected

The repo recorded that gh "cannot read the macOS keychain" in the sandbox. It authenticates from the keychain fine — what fails is certificate verification, because Go's TLS cannot reach com.apple.trustd.agent:

tls: failed to verify certificate: x509: OSStatus -26276

And the trap that made this expensive to find: a single gh call usually succeeds. gh pr view 1831 returned correct JSON while a 25-call burst failed 25/25. Verify with a burst, never one call.

There is a real fix, so the docs no longer present avoidance as the only option:

// ~/.claude/settings.json -> sandbox.network
"enableWeakerNetworkIsolation": true

Read at session start, so it takes effect the next session.

Docs brought to current state

  • AGENTS.md — "total blackout" → "dominant blackout", carrying the measurement, the trust-cache explanation, the burst command, the real error, and the fix.
  • .claude/rules/shipping.md §3 (Bash traps) — the burst-verification trap, cross-referenced to AGENTS.md rather than restating it.

Not changed, because it was verified correct: shipping.md's claim that sandbox.filesystem is disabled: true in ~/.claude/settings.json. It is.

Verification

  • markdown:ratchet — no regressions
  • docs:path-drift — 0 unresolved
  • guard exercised in both directions (table above)

🤖 Generated with Claude Code

https://claude.ai/code/session_01AZX6vCQjxRC22o8Uz16VwK

… fired

The cleanup tool has a guard whose entire purpose is to refuse to present a
report built on failed evidence. AGENTS.md promised it. It exists in the code.
It did not fire, and the reason is one character of logic.

MEASURED, instrumented at the guard's own line, inside the Bash sandbox:

    total rows 72   ->   { FAILED: 69, OK: 3 }

96% of PR lookups failed. The condition was `failed === all`, so the guard
stayed silent, the tool exited 0, and the summary read "0 branches deletable"
— indistinguishable from a genuinely clean repository. That is the precise
failure the guard was written to prevent, surviving inside the guard that was
supposed to prevent it.

Why it is never unanimous: macOS caches TLS trust decisions, so the first few
`gh` calls succeed from cache before it is exhausted. A PARTIAL blackout is
the normal shape of this failure; a total one is the special case. A threshold
set at the special case is a gate that cannot fire.

Now: any failed lookup is reported on its own line (a row whose lookup failed
proves nothing about that branch), and a failure ratio >= 0.5 exits 2.

Verified both directions, which is the point:
  sandboxed    70/73 (96%) -> INFRASTRUCTURE_FAILURE, exit 2
  unsandboxed  0 failures  -> no marker, exit 0, normal report

Also corrected the diagnosis this repo had recorded. It said `gh` "cannot read
the macOS keychain" in the sandbox. It authenticates from the keychain fine —
what fails is CERTIFICATE VERIFICATION, because Go's TLS cannot reach
com.apple.trustd.agent:

    tls: failed to verify certificate: x509: OSStatus -26276

And the practical trap that made this expensive to find: a SINGLE `gh` call
usually succeeds in the sandbox. `gh pr view 1831` returned correct JSON while
a 25-call burst failed 25/25. Verify with a burst, never one call.

There is a real fix, so the docs no longer say "re-run outside the sandbox" as
if avoidance were the only option:

    ~/.claude/settings.json -> sandbox.network.enableWeakerNetworkIsolation: true

read at session start, so it takes effect the NEXT session.

Docs updated to current state rather than left pointing at each other:
- AGENTS.md: "total blackout" -> "dominant blackout", with the measurement,
  the trust-cache explanation, the burst command, the real error, and the fix.
- .claude/rules/shipping.md section 3 (Bash traps): the burst-verification
  trap, cross-referenced to AGENTS.md instead of restating it.

NOT changed, because it was verified CORRECT: shipping.md's claim that
sandbox.filesystem is `disabled: true` in ~/.claude/settings.json. It is.

Verified: markdown:ratchet no regressions; docs:path-drift 0 unresolved; the
guard exercised in both directions above.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AZX6vCQjxRC22o8Uz16VwK
@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing

@coderabbitai

coderabbitai Bot commented Sep 4, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: 41e3e66c-483e-4d0e-a5c7-e5ddf02e42aa

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@supabase

supabase Bot commented Sep 4, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project qmnssrrolpinvwjjnufo because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@vercel

vercel Bot commented Sep 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
helmv3 Ignored Ignored Preview Sep 4, 2026 4:47pm UTC

Request Review

njrini99-code and others added 4 commits September 4, 2026 11:37
…mands does not cover

Self-correction on the previous commit, plus the finding that explains why the
tool broke despite already having an exclusion entry.

WHAT I GOT WRONG. The previous commit asserted "macOS caches TLS trust
decisions, so the first few gh calls succeed from that cache before it is
exhausted." I had not measured that. It was a plausible story fitted to two
data points, which is the exact habit this repo keeps paying for. Measured
properly:

    gh pr view 1831              OK      single call
    3 calls after an idle gap    3/3 OK
    25-call burst                0/25    fails at call #1, not after a warm-up
    3 calls straight after       0/3     still failing; recovers after a pause

"Fails at call #1" kills the cache-exhaustion story outright. The behaviour is
volume-sensitive with time-based recovery; throttling of the system trust
service by a sandboxed client fits, but NOTHING HERE PROVES IT, so the docs now
say the cause is unconfirmed instead of naming one. The operational rule —
verify with a burst, never one call — is what was actually established, and it
is unaffected.

WHAT I MISSED. `sandbox.excludedCommands` already lists `gh *`. That is why
single top-level `gh` calls succeed and why this was so easy to misdiagnose as
"gh works fine here". It does NOT cover `gh` spawned from inside a script or a
Node child process — which is precisely how worktree-lifecycle.mjs calls it. An
exclusion entry that appears to handle a tool while the tool's real call path
stays sandboxed is the same failure class as the guard itself.

SCOPE CORRECTION. The config fix moved from ~/.claude/settings.json to
.claude/settings.local.json (gitignored, helmv3-only). enableWeakerNetworkIsolation
weakens network isolation, and user scope applies that to every project on the
machine — shipping.md's own rule says user-scope grants are not edited from this
repo, and I had walked past it. Raised by the concurrent session, and correct.
The docs now also state plainly that the key is read at session start and is
therefore UNVERIFIED until a restart, with the burst command to confirm it and
what to conclude if it still fails.

quality-gates.md gains the sub-class this all belongs to: a check whose FAILURE
is shaped exactly like its SUCCESS. Three unrelated instances surfaced on
2026-09-04 — the blackout guard at 96% failure reading "0 deletable", a test
whose regex mis-paired on prose apostrophes and passed by asserting over an
empty set, and semgrep's known bad-glob zero-finding mode. One tell every time:
an empty result reported as a good result. That file already exists to name
this; it now names it with cases.

Verified: markdown:ratchet no regressions; docs:path-drift 0 unresolved. The
guard behaviour from the previous commit is unchanged and still exercised in
both directions.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AZX6vCQjxRC22o8Uz16VwK
Second tell for the section added in the previous commit, and this file caused
the case it documents.

The 2026-09-04 sandbox write-up first shipped a mechanism — "macOS caches TLS
trust decisions, so the first few calls succeed before it is exhausted" — that
was fitted to two data points, landed in AGENTS.md, and was false. The third
measurement killed it: the burst failed at call #1, not after a warm-up.

Recorded because of what a wrong cause does that an admitted unknown does not:
it gets reasoned FROM. The natural next "fix" for a warm-cache story is to add
a warm-up call, and someone would have spent a session chasing a mechanism that
does not exist.

Rule: when you can measure the behaviour but not the cause, write the behaviour
and mark the cause UNCONFIRMED. Same failure as the empty-result-reads-as-clean
class above — confidence with nothing under it — one in a check's output, one
in a doc's prose.

Raised by the concurrent session, which named the generalization; the case is
this session's own.

Verified: markdown:ratchet no regressions.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AZX6vCQjxRC22o8Uz16VwK
Three fixes for why worktrees and branches only ever accumulated. All three
are the same shape: a subtraction that could never happen.

1. MERGING DESTROYED THE PROOF THAT PARKING NEEDED.

`delete_branch_on_merge` is TRUE on this repo, so GitHub deletes a branch's
remote at merge. The park path required a remote tip to prove the commits
survive removing the directory. So the evidence parking depended on was
destroyed by the exact event that made parking correct.

Measured 2026-09-04: 0 of 25 worktrees parkable — not one of them because it
was in use. Three (PRs #1793, #1797, #1819) printed UNKNOWN_REMOTE, "commits
here may exist nowhere else", on the same row whose branch column read
DELETE_MERGED_EXACT. One report calling the same commits both provably-in-main
and possibly-nowhere.

This is #1654's defect, which was fixed for BRANCH deletion and never carried
to the PARK path — AGENTS.md even states the principle ("never by a remote tip
— delete_branch_on_merge removes that exactly when the branch becomes safe")
while the park code did the opposite two functions away.

A PR merged at this exact tip now counts as durability proof (stronger than a
remote tip: the commits are in main) and satisfies the workspace gate, because
merging IS the owner saying the work is done. Four facts, three already proven
before control reaches the gate: clean, idle, identified, merged-at-this-OID.
#1681's rule is untouched — an OPEN PR still requires the marker.

After: 3 worktrees parkable, 4 branches deletable, and every protection intact
(the concurrent session's ACTIVE checkout, deploy-main detached, both open PRs).

2. THE EXCEPTION SHIPPED DEAD ON THE FIRST ATTEMPT.

Worth recording rather than quietly fixing: prHeadSha was never passed into the
worktree facts, so `mergedAtThisTip` evaluated false for every checkout and the
new code changed nothing. It did not error. It just never fired — the same
class as the guard whose threshold could not be met, found twice in one day.
The test pins the field at both gates.

3. TWO CONTRADICTORY node_modules POLICIES, LIVE ON ONE MACHINE.

new-worktree.sh's header rejects `ln -s node_modules` because two branches with
different lockfiles test against whichever tree was installed last. That reason
is correct but CONDITIONAL, and was written as though lockfiles always differ —
most task branches never touch package-lock.json. Meanwhile
worktree.symlinkDirectories in ~/.claude/settings.json symlinks node_modules
for Claude Code's own worktrees, and sessions had started hand-symlinking to
dodge the 3.8 GiB cost, without the lockfile check that makes it safe.

ensure-worktree-deps.mjs now checks the condition instead of assuming it:
byte-identical lockfile plus an installed canonical tree => share it (0 bytes);
anything else => a real install, original warning intact. Verified both ways.

4. IT NOW RUNS AT MERGE TIME.

`npm run worktrees:postmerge` (git fetch --prune + --retire), wired to fire
after `gh pr merge` via a PostToolUse hook. GitHub already deletes the REMOTE
branch on merge; nothing ever deleted the LOCAL one, which is why 62 local
branches stood against 49 remote. The hook deliberately does not pipe or
swallow its exit code — a cleanup that reports success when it could not run is
the failure this whole PR is about.

Verified: typecheck clean; eslint clean on changed files; 10/10 new classifier
tests (collected by glob, so CI runs them); markdown:ratchet no regressions;
docs:path-drift 0 unresolved; lifecycle exercised live before and after.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AZX6vCQjxRC22o8Uz16VwK
…ious commit

They were extracted from abandoned workflow branches to CHECK whether they
still passed against main. They do NOT — they encode older error strings
('Not authenticated' vs 'Unauthorized: no golf session') and an older enum
('no_gap' vs 'below_threshold'). Main's behaviour is correct; the tests are
stale, and merging them would turn CI red for no reason.

They reached the index because `git checkout <branch> -- <paths>` STAGES what
it extracts, so they were already staged before the explicit `git add` — the
'always add explicit paths' rule does not protect against something staged
earlier by a different command.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AZX6vCQjxRC22o8Uz16VwK
@njrini99-code njrini99-code changed the title fix(tooling/docs): the blackout guard required unanimity, so it never fired fix(worktrees/docs): the subtractions that could never happen Sep 4, 2026
njrini99-code added a commit that referenced this pull request Sep 5, 2026
…e has ended

`open-pr-residue` requires the key set to equal the open-PR set, and a row for
an in-flight PR lives at that PR's own head rather than on main — so #1836's
row rides its own branch. Verified both directions: without it the check names
#1836 as unclassified; with it, it does not.

Also deletes #1725 and #1738, both MERGED, both past the transitional grace
this file's own `$comment` defines — the tool's output says in as many words to
delete them "in the PR you are already opening", and that is this one. Leaving
them is the exact failure the comment describes: a current-state registry
asserting things that stopped being true.

**`open-pr-residue` still FAILS, for reasons this PR does not own.** Twelve
open PRs carry no row at all — #1834, #1833, #1832, #1831, #1829, #1827, #1759
and five dependabot PRs. That was already true before this commit and is not
mine to reconcile; a row is a statement about someone else's work. Recording it
here so the red is legible rather than mysterious.

Note for merge order: #1835 also edits this file, so whichever lands second
resolves that conflict.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019euu9jtJM6WvMVj17coqZ1
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