Skip to content

fix(gate): rule 3d named a remedy that cannot reach the worktree it refused (BACKLOG #1057) - #261

Merged
wshallwshall merged 6 commits into
mainfrom
claude/gate-3d-remedy-1057
Aug 8, 2026
Merged

fix(gate): rule 3d named a remedy that cannot reach the worktree it refused (BACKLOG #1057)#261
wshallwshall merged 6 commits into
mainfrom
claude/gate-3d-remedy-1057

Conversation

@wshallwshall

Copy link
Copy Markdown
Collaborator

Fixes BACKLOG #1057. 3 files, +300 -7 -- one BACKLOG banner line, the gate, and a new test file.

THE DEFECT. Rule 3d refused an operation and then named a remedy that could not act on the worktree
it had just refused. The gate has already resolved the path by that point, so the deny now names the
actual directory.

⭐ THE FINDING GREW UNDER TEST, and the correction is the interesting part. It was reported as
affecting one branch of the deny. Testing showed the own-tree branch printed the LITERAL PLACEHOLDER
-Name <directory-name> for BOTH families -- the right tool, named correctly, and still not
pasteable. So the defect was in both branches and both families, not the one originally filed.

⚠️ TWO OF THE TESTS WERE FALSE GREENS FIRST, and both are the deny-TEXT class:

  1. Every deny opens by quoting the command back. Searching the WHOLE reason for a runnable
    git ... worktree remove therefore matched the refusal's own ECHO, so the Claude-managed
    own-tree case went GREEN over a deny that offered nothing runnable. Now scoped to the text
    after "What to do instead:".
  2. The remedy deliberately SAYS prune-merged.ps1 cannot help here, so a bare token search failed
    on the prohibition itself. The property is "no runnable invocation", NOT "the name never
    appears" -- the same distinction test_coord_claim_liveness.py already draws for -Force.

EVIDENCE, with the negative control RUN rather than assumed: 6/6 green post-fix; reverting the fix
turns 4 red while the 2 that stay green are the controls. Full gate suite 247 passed, no regression.
Quartet green; leak scan clean on all three files.

⛔ DELIBERATELY NOT TOUCHED: #1064's fail-open, where git worktree remove <relative-primary> is
measured ALLOW. That is a separate item and a security decision rather than a text change.

COORDINATION. scripts/hooks/worktree_gate.ps1 is under active work by another session. This change
was written only after that session freed the file, and the insert point was re-measured immediately
before writing rather than trusted from an earlier reading. Verified here: 9f96fdfe's edit does not
overlap that session's committed changes (3-way merge against their tip, base-anchored: CLEAN).

PROVENANCE. Cherry-picked from claude/analyze-issues-1057-1060-1063-9e9220 onto current main so this
stands alone: on that branch the commit sits ON TOP of PR #260's three anchoring commits, and a PR
cut from that tip would have duplicated #260's content. #260 stays frozen at a042c0f and is
untouched.

…efused (BACKLOG #1057)

The refusal was right and the sentence after it was not. A caller told "no" is handed a
command to run instead, and for the population that most often reaches this rule that
command throws.

  * `remove.ps1 -Name <dir>` resolves to <repo-parent>/<repo-leaf>-<dir>. new.ps1:79-81
    ASSERTS that shape after deriving it, so the <primary>-<name> sibling family is the only
    one it can produce and the only one remove.ps1 can resolve; anything else fails its
    Test-Path and throws "No such worktree".
  * `prune-merged.ps1` excludes anything with a `.claude/worktrees/` path segment OUTRIGHT,
    by its own header, and -Name cannot reach them either. The exclusion is deliberate:
    those are the trees a live session gets relocated into.

Census on this clone 2026-08-06: 45 sibling worktrees, 8 Claude-managed, 4 other -- and all
six live sessions sat in the 8. Verified against the INSTALLED gate, not only the repo copy.
Same defect as #1032 one rule over, where rule 3b printed a new.ps1 command new.ps1 refuses
to run. A refusal the reader cannot act on is the standing invitation to route around the
guard, which costs more than the refusal buys.

The deny now branches on worktree family. The family neither script can serve gets a literal
`git -C "<primary>" worktree remove "<path>"` plus a line saying why prune-merged.ps1 does
not apply. The sibling family KEEPS prune-merged.ps1 -- it is dry-run by default and consults
occupancy, so it is strictly better there than a bare git command, and the fix must not
become "stop naming the scripts". A test pins that.

WRITING THE TEST ENLARGED THE DEFECT. The own-tree branch printed the literal placeholder
`-Name <directory-name>` for BOTH families: the right tool, named correctly, and still not
pasteable. The gate has already resolved the path by then, so it now names the directory.

REMEDY TEXT ONLY. Which worktrees rule 3d refuses is untouched, and nothing security-relevant
reads the classification -- which is what makes a misclassification cheap here and expensive
in rule 3c. Failure direction is pinned by test rather than asserted: a junction or UNC
spelling breaks the prefix match, classifies NOT-sibling, and the not-sibling remedy is valid
for every family including siblings. The dangerous direction needs a non-sibling to
SPURIOUSLY match <primary>-<name>, which an unresolved alias makes less likely, not more.
#1064's measured rule 3d fail-open is deliberately not touched here.

TWO ASSERTIONS IN THE NEW TESTS EXIST BECAUSE THE FIRST VERSIONS MEASURED THE WRONG THING:

  * every deny opens by quoting the command back, so searching the WHOLE reason for a runnable
    `git ... worktree remove` matched the refusal's own echo, and the own-tree case for the
    Claude-managed family went GREEN over a deny that offered nothing runnable. Assertions are
    now scoped to the text after "What to do instead:";
  * the remedy deliberately says prune-merged.ps1 CANNOT help here, so a bare token search
    fails on the prohibition itself. The property is "no runnable invocation", not "the name
    never appears" -- the same distinction test_coord_claim_liveness.py already draws for
    -Force, and for the same reason.

Negative control run, not assumed: reverted, 4 of the 6 cases go red and the 2 that stay
green are the controls. Full gate suite 247 passed. Rule 3d is byte-identical between main
and claude/worktree-gate-backlog-061bdc's tip, checked before writing, so this cannot revert
or duplicate that session's in-flight work.

This closes the REMEDY half of #1057. The occupancy half is untouched and the item stays open:
rule 3d still has no occupancy or authorship signal, so an unoccupied worktree is still
refused rather than allowed.
@wshallwshall
wshallwshall enabled auto-merge (squash) August 6, 2026 21:23
auto-merge was automatically disabled August 6, 2026 23:52

Pull request was closed

@wshallwshall wshallwshall reopened this Aug 6, 2026
@wshallwshall
wshallwshall enabled auto-merge (squash) August 6, 2026 23:53
…ACKLOG #1057)

`test (ubuntu-latest, py3.14)` failed on PR #261. The junction test shells out to
`cmd /c mklink /J` and guarded with `if made.returncode != 0: pytest.skip(...)`. There is no
`cmd` on Linux, so subprocess.run RAISES FileNotFoundError before there is a returncode to
inspect -- the guard never runs and the test errors.

PLATFORM-MASKED, AND WINDOWS IS THE PLATFORM THAT CANNOT SEE IT: the guard is only reachable
where `cmd` exists, so a green local quartet on Windows was structurally incapable of
catching this. Same shape rule 3d's own comment already records, where a construct "passed
on Windows and failed on the Linux CI leg".

Gate on the platform FIRST, and keep the OSError arm: `cmd` existing does not mean junction
creation is permitted, so the two guards catch different things and both are load-bearing.

VERIFIED BY REPRODUCING THE LINUX CONDITION, not by re-running on Windows:

    cmd absent, unfixed  ->  FileNotFoundError, test ERRORS   (the CI failure, reproduced)
    cmd absent, fixed    ->  SKIPPED via the OSError arm
    os.name=posix, fixed ->  SKIPPED via the platform gate
    Windows, real cmd    ->  6 passed, unchanged

The first probe written for this was WRONG and is worth recording: patching `os.name` alone
does not reproduce Linux, because `cmd` still exists here -- the unfixed test happily created
a junction and PASSED. A control that cannot see the defect is not a control. Making
subprocess.run raise for `cmd` is the faithful reproduction, and it discriminates.

That also settles the OSError arm empirically rather than by argument: with `cmd` absent and
os.name still "nt", the platform gate does NOT fire and the OSError arm is what prevents the
error.

Full gate suite 247 passed. Patch authored by the coordinator under route (c); the claim gate
correctly refused their commit because another worktree held #1057, so it is committed here
rather than forced past. It then refused THIS worktree too, for the same reason and equally
correctly -- entitlement is keyed on the committing worktree, and building the fix in a fresh
worktree moved me out of my own claim. Salvaged the documented way: released my own claim
without -Force, re-took it here.
auto-merge was automatically disabled August 7, 2026 01:21

Pull request was closed

@wshallwshall wshallwshall reopened this Aug 7, 2026
@wshallwshall
wshallwshall enabled auto-merge (squash) August 7, 2026 01:22
…KLOG #1057)

`test (windows-2025, py3.14)` timed out on PR #261: "The action 'Tests (pytest)' has timed
out after 36 minutes". Not a flake and not a defect in the gate change -- the leg has almost
no headroom and this file's 249 lines consumed what was left.

    #261  05:00:57 -> 05:38:03 = 37m06s   step timed out at 36m
    #260  04:59:22 -> 05:32:31 = 33m09s   success
    #259  22:00:10 -> 22:38:30 = 38m20s   success

step_timeout is 36 for windows-2025, read from ci.yml's matrix rather than inferred from the
error text. A suite habitually finishing near 33 against a 36 cap has ~17% headroom.

THE TELL THAT IT IS ELAPSED TIME AND NOT CONTENT: the timeout named
tests/test_tls_floor_probe.py, while #261 touches worktree_gate.ps1, its tests and one
BACKLOG line -- nothing TLS-related. Location and change do not line up. Also ruled out:
environmental (the same leg is SUCCESS on 13 other open PRs), and the exit-139 native-crash
class of #264 (this is a clean timeout, not a crash).

THE FIX HERE IS TO MAKE THIS FILE CHEAPER, NOT TO RAISE THE CAP. step_timeout is deliberately
held under job_timeout (46) so a process-level deadlock below pytest surfaces as a STEP
failure rather than a job kill; ci.yml says so explicitly. Raising it would trade a real
diagnostic for a green tick.

The `repo` fixture is subprocess-bound (six `git` spawns) and READ-ONLY -- every test feeds a
payload to the hook, which DENIES before git runs, so no worktree is ever actually removed
against it. Function scope paid for it six times: 36 process launches, cheap on Linux and
expensive on a hosted Windows runner. Module-scoped it removes 30 of those. Measured locally:
10.66s -> 5.26s, a 51% cut, with setup no longer appearing in the top durations at all.

The docstring records WHY sharing is safe rather than asserting that it is, and says what
would make it unsafe: a case that really removes a worktree needs its own function-scoped
fixture.

Files #1084 for the underlying condition, which outlives this file. The margin is invisible
until it is gone -- nothing reports "the Windows leg finished at 92% of its budget" -- and
the measured spread across three PRs the same day (33m09s / 37m06s / 38m20s) means runner
variance alone is comparable to the headroom, so a re-run can flip the verdict with nothing
changed. That is also why #261 must not be merged on a lucky green.

Diagnosis by the coordinator, who ruled out three alternative explanations against evidence
before accepting the elapsed-time one, and who recorded a prediction before re-running rather
than after.
docs/BACKLOG.md only, and the conflict was structural rather than semantic: #268 (#1087) and
this branch (#1084) both append an item to the file's tail, so every backlog PR that merges
invalidates every other open one. Three landed in a row tonight (#263, #266, #268), which is
what made the tail a serialization point.

Resolved by keeping BOTH items in numeric order. Merged rather than rebased so both commits
and the PR's arming survive.

VERIFIED BY INTENT, not by absence of conflict markers:
  * no duplicate item numbers anywhere in the file;
  * every heading present on origin/main is still present here -- set difference is empty;
  * the only heading this branch adds is #1084, which is the one it is for;
  * backlog_status_check.py exit 0, 346 items each declaring exactly one status.

The conflict set was established with `git merge-tree --write-tree`, not from a changed-files
list: `gh pr view --json files` lists files CHANGED, which is a different question and
inflated another session's conflict count threefold earlier tonight.

Entitlement is unaffected -- ledger_check.py consults owns() only for ADDED headings, and
#1084 is allocated to this worktree, which is also why this resolution was not delegable.
wshallwshall added a commit that referenced this pull request Aug 7, 2026
… wrong instrument

#1084 was filed the same day, for the same leg and the same cap, and neither
item referenced the other. It is not a harmless duplicate: the two CONTRADICT
each other on the central action. #1084 says "Do NOT simply raise the cap";
#1096 says the cap must be re-derived. Left alone, whichever is worked first
leaves the survivor standing as an open item asserting the work was wrong.

The collision was invisible from this worktree. #1084 is not on main -- it
lives only on origin/claude/gate-3d-remedy-1057 (PR #261, open) -- so reading
docs/BACKLOG.md here shows no conflict, and backlog_status_check cannot see
one either. The cross-reference is therefore labelled FORWARD-LOOKING in the
item, per the rule that a citation which does not resolve yet must say so.

The contradiction resolves, and #1084 is not wrong in spirit -- its premise is
wrong. Its "~92% of step_timeout, about 17% headroom" comes from comparing JOB
wall time across three PRs (33m09s / 37m06s / 38m20s) against a STEP cap. On
the step the figure is 35:13 against 36:00 = 97.8% of budget, 2.2% headroom.
That is the same job-for-step substitution ci.yml already records at least
three earlier sessions making while triaging this very cap.

That changes what "protect the diagnostic" implies, which is the whole
disagreement. #1084 is right that step_timeout sits under job_timeout so a
deadlock below pytest surfaces as a step failure, and right that raising a cap
to buy a green tick would trade that away. But at 2.2% headroom, against a
distribution where EVERY passing run exceeds the file's own 28:00 re-derive
trigger, the cap fires on a healthy suite as readily as on a deadlock -- so
re-deriving it RESTORES the diagnostic rather than surrendering it.

Also absorbed from #1084 so closing it loses nothing: its three-wrong-answers
triage, the step-under-job rationale, its fix directions (margin reporting is
the cheapest and should likely happen regardless; splitting the Windows leg;
the measured 51% cut on tests/test_worktree_gate_remedy_families.py from
removing 30 of 36 git spawns), and its #1000 negative-control requirement.

Added stronger evidence than either item had alone: a SAME-COMMIT flip. Run
31192717684 (fix-1006-absence-mutation, head 27d7746) failed at 36:07 on
attempt 1 and passed at 33:14 on attempt 2. #1084 inferred a flip from three
DIFFERENT PRs, which is not identical content; this is.

The 43-of-43 figure is NOT rewritten to 45. An independent re-derivation later
the same day reproduced 35:13 to the second and the same five kills, with the
pool grown to n=68 and 45 of 45 above the trigger. It is recorded as a second
measurement with its own pool rather than folded in, because a number edited
away from the pool that produced it is exactly the rot ci.yml warns about.

Recommended disposition stated in the item: keep #1096, close #1084 as merged
into it, correct #1084's numbers rather than carrying them forward. There is
no allocation collision -- 1084 and 1096 are different numbers, only the
subject collided.

Gates: backlog_status_check.py --min-items 150 OK (366 items, each declaring
exactly one status), ledger_check.py --ci OK. Live table still 109 rows with
contiguous ranks; #1096 still parses open via parse_items.
@wshallwshall

Copy link
Copy Markdown
Collaborator Author

Coordinator: BACKLOG #1084 on this branch collides in SUBJECT with #1096, and the two contradict each other. Nothing blocks this PR; please read before it lands.

This is not a number collision -- 1084 and 1096 are different numbers, correctly allocated. It is a
subject collision, and it was invisible to every automatic check: #1084 exists only on this branch
(origin/claude/gate-3d-remedy-1057), not on main, so reading docs/BACKLOG.md from any other worktree
shows no conflict and backlog_status_check cannot see one either.

Same defect, same day, same leg, same cap. #1096's kill table literally contains #1084's originating run
(this PR's 31149117314). The problem is that they disagree on the central action: #1084 says "Do NOT
simply raise the cap"
; #1096 says the cap must be re-derived. Whichever is worked first, the other stands
as an open item asserting the work was wrong.

Where the disagreement comes from -- a premise, not a judgement

#1084's headline is "~92% of its step_timeout ... about 17% headroom", and its Source says it was
"diagnosed by comparing job wall time across three PRs". Those numbers -- 33m09s / 37m06s / 38m20s -- are
JOB wall times measured against a STEP cap.

Measured on the step, filtered on step conclusion, ?filter=all so runs killed at the cap are not hidden
behind their passing re-runs:

#1084 (job wall time) measured on the step
budget consumed ~92% 97.8%
headroom ~17% 2.2% (35:13 against 36:00)

ci.yml records at least three earlier sessions making this exact job-for-step substitution while
triaging this same cap, so this is a known trap here rather than a criticism.

Why that flips the conclusion rather than just sharpening it

Your reasoning in (3) is right and I have kept it: step_timeout sits under job_timeout deliberately so
a deadlock below pytest surfaces as a step failure instead of a job kill, and raising a cap merely to
buy a green tick would trade that diagnostic away.

But at 2.2% headroom, against a distribution where every passing run today exceeds ci.yml's own
stated 28:00 re-derive trigger, the cap fires on a healthy suite as readily as on a deadlock. It has
stopped discriminating. So re-deriving it restores the diagnostic rather than surrendering it -- which
is the opposite of the trade you were guarding against, from the same principle.

Additional evidence neither item had alone: a same-commit flip. Run 31192717684
(fix-1006-absence-mutation, head 27d7746d) failed at 36:07 on attempt 1 and passed at 33:14 on
attempt 2. #1084 inferred a flip from three different PRs, which is not identical content; this is.
Also, two push runs on main (7ecff8ae, b78214f5) were killed at 36:08 -- main itself is
intermittently red on this leg.

What I did, and what I did not

I amended #1096 (PR #278, unarmed) to cite #1084 as a forward-looking reference -- flagged as such,
since it does not resolve until this PR lands -- and to absorb everything #1084 has that #1096 lacked:
the three-wrong-answers triage, the step-under-job rationale, your fix directions (margin reporting as the
cheapest and probably-do-it-anyway option, splitting the Windows leg, and the measured 51% cut on
test_worktree_gate_remedy_families.py from removing 30 of 36 git spawns), and the #1000 negative-control
requirement.

I did not touch #1084 or anything else on this branch. Editing another session's landed item is the
kind of quiet change that becomes an undiscoverable defect; the visible inconsistency is the discoverable
one. The recommendation recorded in #1096 is to close #1084 as merged into it and correct its numbers
rather than carry them forward -- but that is your call, not mine, and this PR does not need to change
for it.

If you would rather keep #1084 as the surviving item, say so and I will strip the superseding language from
#1096 instead. Either is fine; two items arguing is not.

wshallwshall added a commit that referenced this pull request Aug 7, 2026
…on main (#278)

* backlog: file #1096, the windows-2025 Tests (pytest) cap is exceeded on main

ci.yml sets step_timeout 36 for both Windows legs and, in the same comment
block, names its own re-derivation trigger: re-derive if a windows-2025
Tests (pytest) step is ever seen above 28:00.

Measured 2026-08-07 over all 80 ci.yml runs created that day, jobs fetched
with filter=all so an attempt killed at the cap is not hidden behind its
passing re-run, timing the STEP and filtering on step conclusion:

  windows-2025 Tests (pytest)   max passing 35:13   n=65 steps / 43 success
  cap 36:00 -> 47 seconds of margin, 1.022x
  passing steps above the 28:00 trigger: 43 of 43
  killed at 36:0x: 5, two of them push runs on main (7ecff8a, b78214f)

So the trigger is not merely fired, it is universally exceeded, and the cap
is failing green suites. ci.yml already records this shape once at 26:00
(PR #119, killed at 26:07 with zero tests failing, diagnosed there as
coin-flipping against the cap) and already admits 36:00 failed its own
spread rule when it was chosen, needing roughly 37:00 to hold.

Measured the step rather than the job deliberately: ci.yml records at least
three sessions substituting job durations while triaging this same cap, and
a job-duration reading of this pool gives 33-40 minutes, which invites the
wrong conclusion that the cap is comfortable.

job_timeout must be re-derived in the same act. The job carries two
step_timeout-gated pytest steps and its 46 is sized against the observed
sum, not 2 x step_timeout, so raising step_timeout alone moves the kill to
the job cap, where no step conclusion is reported and the instrument used
to measure this is lost.

Ledger mechanics, stated separately because they are independent claims:

  - Number allocated with scripts/coord/alloc.ps1, not grepped. Row added
    in this same commit at rank 6, ranks 6..108 renumbered to 7..109.
  - The renumber was bounded to the live table (lines 180..287). The file
    also holds a superseded 134-row 2026-07-10 table; an assertion checks
    it comes out byte-identical, and it does.
  - The four Distribution census lines were re-derived, not carried
    forward: Value 7 -> 8, Difficulty 3 -> 40, P1 -> 9, quick win -> 34,
    sum 108 -> 109.
  - The "Every one of the 102 open items" sentence was left ALONE on
    purpose. It is a dated claim about the 2026-08-03 re-scoring pass, not
    a live census, and bumping it would assert that pass scored items that
    did not exist yet. Prior passes appear to have bumped it; that looks
    like the actual source of its drift.

MY FILING IS CORRECT AND THE CENSUS IS STILL WRONG -- these are separate
claims and this commit only makes the first. Verified in both directions
with parse_items imported from backlog_status_check.py rather than a
hand-rolled scan, which surfaces PRE-EXISTING drift this commit does not
touch: 52 open items carry no row, 11 rows name an item that is not open,
and parse_items counts 150 open items against a census asserting 109. Those
two sets cancel on a total, which is why a matching sum never caught it.
That reconcile is its own pass and needs 52 items scored.

Gates run locally: backlog_status_check.py --min-items 150 OK (366 items,
each declaring exactly one status), ledger_check.py --ci OK.

* backlog #1096: reconcile with #1084, which is the same defect under a wrong instrument

#1084 was filed the same day, for the same leg and the same cap, and neither
item referenced the other. It is not a harmless duplicate: the two CONTRADICT
each other on the central action. #1084 says "Do NOT simply raise the cap";
#1096 says the cap must be re-derived. Left alone, whichever is worked first
leaves the survivor standing as an open item asserting the work was wrong.

The collision was invisible from this worktree. #1084 is not on main -- it
lives only on origin/claude/gate-3d-remedy-1057 (PR #261, open) -- so reading
docs/BACKLOG.md here shows no conflict, and backlog_status_check cannot see
one either. The cross-reference is therefore labelled FORWARD-LOOKING in the
item, per the rule that a citation which does not resolve yet must say so.

The contradiction resolves, and #1084 is not wrong in spirit -- its premise is
wrong. Its "~92% of step_timeout, about 17% headroom" comes from comparing JOB
wall time across three PRs (33m09s / 37m06s / 38m20s) against a STEP cap. On
the step the figure is 35:13 against 36:00 = 97.8% of budget, 2.2% headroom.
That is the same job-for-step substitution ci.yml already records at least
three earlier sessions making while triaging this very cap.

That changes what "protect the diagnostic" implies, which is the whole
disagreement. #1084 is right that step_timeout sits under job_timeout so a
deadlock below pytest surfaces as a step failure, and right that raising a cap
to buy a green tick would trade that away. But at 2.2% headroom, against a
distribution where EVERY passing run exceeds the file's own 28:00 re-derive
trigger, the cap fires on a healthy suite as readily as on a deadlock -- so
re-deriving it RESTORES the diagnostic rather than surrendering it.

Also absorbed from #1084 so closing it loses nothing: its three-wrong-answers
triage, the step-under-job rationale, its fix directions (margin reporting is
the cheapest and should likely happen regardless; splitting the Windows leg;
the measured 51% cut on tests/test_worktree_gate_remedy_families.py from
removing 30 of 36 git spawns), and its #1000 negative-control requirement.

Added stronger evidence than either item had alone: a SAME-COMMIT flip. Run
31192717684 (fix-1006-absence-mutation, head 27d7746) failed at 36:07 on
attempt 1 and passed at 33:14 on attempt 2. #1084 inferred a flip from three
DIFFERENT PRs, which is not identical content; this is.

The 43-of-43 figure is NOT rewritten to 45. An independent re-derivation later
the same day reproduced 35:13 to the second and the same five kills, with the
pool grown to n=68 and 45 of 45 above the trigger. It is recorded as a second
measurement with its own pool rather than folded in, because a number edited
away from the pool that produced it is exactly the rot ci.yml warns about.

Recommended disposition stated in the item: keep #1096, close #1084 as merged
into it, correct #1084's numbers rather than carrying them forward. There is
no allocation collision -- 1084 and 1096 are different numbers, only the
subject collided.

Gates: backlog_status_check.py --min-items 150 OK (366 items, each declaring
exactly one status), ledger_check.py --ci OK. Live table still 109 rows with
contiguous ranks; #1096 still parses open via parse_items.
…ded item

Resolves the docs/BACKLOG.md conflict against main at f74c2ab.

Subject wording note: the claim gate refused an earlier subject that named the
dropped item as "BACKLOG #1084", because that phrasing on a code-touching commit
means "I am building this item" and the item is unclaimed. It is not being built
here, it is being REMOVED, so the original subject was simply inaccurate about
what the commit does. The number is stated throughout this body instead.

BACKLOG #1084 is DROPPED from this branch rather than landed, on the owner's
instruction. It is superseded by #1096, which is already on main, and the two
contradict each other on the central action: #1084 says "Do NOT simply raise the
cap", #1096 says the cap must be re-derived. #1084's premise is the reason it
loses. Its headline "~92% of step_timeout, about 17% headroom" divides a JOB wall
time by a STEP cap; measured on the step it is 97.8% of budget with 2.2% headroom.
The two figures are also inconsistent with each other on their face, since 92%
used is 8% headroom and 17% would require a step near 30 minutes.

#1096 already carries a section anticipating this collision and asking that the
reconciliation be written into whichever item survives. That section previously
predicted the cross-reference would resolve "once #261 lands". It is updated here
in the same act, because #261 lands WITHOUT #1084 and the old wording would have
been left permanently promising a resolution that can never arrive.

The #1084 number stays allocated and burned rather than reused.

Verified by count against both parents rather than by inspection:
  HEAD 151   main 178   merged 178
  lost-from-HEAD [1084]  -- exactly the intended drop, nothing else
  lost-from-main []      -- main fully preserved, including #1101
  invented []            -- no headings created

An earlier resolution of this same conflict was REFUSED by that check: it was
built against a main that predated #1101, and the check reported lost-from-main
[1101]. Rebuilt against f74c2ab.

Content checks, matching main exactly:
  adr/0094-tls-expiry-relaxation.md (rotted)                  0
  adr/0094-granular-expiry-only-tls-relaxation.md (correct)   1
  BACKLOG.md:<line> anchors                                   5

The #1057 banner update this PR exists to make is preserved.

scripts/hooks/worktree_gate.ps1 auto-merged with no markers, so it was checked by
content in both directions rather than trusted: all 64 lines this branch adds and
all 86 lines main adds are present in the merged file.

Gates: backlog hygiene 19 passed; the repo-wide link gate reports 0 genuine
unresolved links.
@wshallwshall

Copy link
Copy Markdown
Collaborator Author

Conflict resolved, and BACKLOG #1084 is DROPPED from this branch

Resolved by the coordinator at the owner's instruction. This PR no longer files #1084. The gate fix and the #1057 banner update — the work this PR exists to do — are untouched.

Why #1084 is dropped rather than landed

It is superseded by #1096, already on main, and the two contradict each other on the central action: #1084 says "Do NOT simply raise the cap"; #1096 says the cap must be re-derived. #1084 loses on its premise. Its headline "~92% of step_timeout ... about 17% headroom" divides a JOB wall time by a STEP cap. Measured on the step it is 97.8% of budget, 2.2% headroom. The two figures are also inconsistent with each other on their face — 92% used is 8% headroom, and 17% would require a step near 30 minutes.

#1096 already carried a section anticipating this collision and asking that the reconciliation be written into whichever item survives. That section predicted the cross-reference would resolve "once #261 lands". It is updated in this same commit, because #261 now lands without #1084 and the old wording would have been left permanently promising a resolution that can never arrive.

The #1084 number stays allocated and burned rather than reused.

Verification, by count against both parents rather than by inspection

HEAD 151   main 178   merged 178
lost-from-HEAD [1084]   <- exactly the intended drop, nothing else
lost-from-main []       <- main fully preserved, including #1101
invented []             <- no headings created

An earlier resolution of this same conflict was REFUSED by that check. It was built against a main that predated #1101, and the check reported lost-from-main [1101]. Rebuilt against f74c2ab3. The count is the instrument; the eyeball would have passed it.

Content checks, matching main exactly — these were supplied by the session that rewrote BACKLOG.md in #283, to catch a keep-both resolution silently restoring what their commits removed:

check resolved main
adr/0094-tls-expiry-relaxation.md (rotted) 0 0
adr/0094-granular-expiry-only-tls-relaxation.md 1 1
BACKLOG.md:<line> anchors 5 5

scripts/hooks/worktree_gate.ps1 auto-merged with no conflict markers, which is the silent half of the wrong-base pair — a clean exit code is exactly what cannot be trusted there. Checked by content in both directions instead: all 64 lines this branch adds and all 86 lines main adds are present in the merged file.

Gates run locally: backlog hygiene 19 passed; test_worktree_gate_remedy_families.py 6 passed; the full gate-related selection 455 passed, 18 skipped (note: that is a scoped -k run which deselected 11,128); the repo-wide link gate that landed in #281 reports 5,359 links / 347 files, all resolve — measured from a short path, because a deep scratch worktree produces 42 spurious cannot read failures at 263 characters against Windows' 260-character limit.

@wshallwshall
wshallwshall merged commit ebacd57 into main Aug 8, 2026
34 checks passed
@wshallwshall
wshallwshall deleted the claude/gate-3d-remedy-1057 branch August 8, 2026 13:58
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