Skip to content

fix(coord): claim -List measured age and called it staleness - #106

Merged
wshallwshall merged 2 commits into
mainfrom
claim-liveness
Aug 2, 2026
Merged

fix(coord): claim -List measured age and called it staleness#106
wshallwshall merged 2 commits into
mainfrom
claim-liveness

Conversation

@wshallwshall

Copy link
Copy Markdown
Collaborator

Found by another session, which nearly acted on it.

The defect

claim.ps1 -List printed this once a claim passed 12h:

[STALE ~21h -- release it if that session is gone]

That measures how long the work has run. It says nothing about whether anyone is still doing it — while recommending the destructive action and withholding the one fact you'd need to decide.

Measured today, on this repo:

7   ... held by …-inbound-http-sync-reply   [STALE ~21h -- release it if that session is gone]

$ git -C …-inbound-http-sync-reply log -1
2 minutes ago   Merge remote-tracking branch 'origin/main' into inbound-http-sync-reply

The holder had committed two minutes earlier. A session was told by its owner to clean up stale claims, read that line, and checked the holder before acting. Had it followed the advice, #7 would have been freed for a second session to start building the ADR 0023 deferred tail while someone was mid-flight on it — the exact duplicate-build this registry exists to prevent, arrived at by following the tool's own recommendation.

The fix

Report liveness, not age:

state rendering
worktree missing [HOLDER GONE -- worktree no longer exists; release with -Force]
holder committed recently [held 21h; holder last committed 0h ago]
holder quiet ≥12h … -- QUIET, confirm with the holder before releasing
can't tell [held Nh; holder liveness UNKNOWN -- confirm before releasing]

A long claim is the normal shape of long work. Only a genuinely quiet holder is flagged now — and as "confirm before releasing", never "release it". Worktree-gone is the only state safe to act on unasked, and it's the only one that still recommends a release.

The catch says so out loud

It sets an explicit [liveness check FAILED -- treat as unknown, confirm before releasing] rather than leaving the annotation blank. An empty annotation reads as "nothing notable about this claim" — the same silent-instrument failure the age signal had: accurate about what it measured, mute about what it could not.

Verification

live holder            → [held 21h; holder last committed 0h ago]      ← was "STALE, release it"
missing worktree       → [HOLDER GONE ... release with -Force]
non-git directory      → [held 21h; holder liveness UNKNOWN ...]
-Take / -Release       → unchanged (round-tripped a self-test key)
parses clean

One file, +33/−5. No behaviour change to claiming or releasing — only to what -List tells you.

🤖 Generated with Claude Code

@wshallwshall
wshallwshall enabled auto-merge (squash) July 31, 2026 16:00
`-List` printed "[STALE ~Nh -- release it if that session is gone]" once a
claim passed 12h. That measures how long the WORK has run and says
nothing about whether anyone is still doing it, while recommending the
destructive action and withholding the fact you would need to decide.

Measured on this repo today: claim #7 rendered "STALE ~21h -- release it
if that session is gone" while its holder had committed TWO MINUTES
earlier. A session was told by its owner to clean up stale claims, read
that line, and checked the holder before acting. Had it followed the
advice, #7 would have been freed for a second session to start building
the ADR 0023 deferred tail while someone was mid-flight on it -- the
exact duplicate-build this registry exists to prevent, reached by
following the tool's own recommendation.

Reports liveness instead: worktree missing (the only state safe to act on
unasked), or hours since the holder's last commit. A long claim is the
normal shape of long work, so 21h with a commit an hour ago now reads as
active rather than abandoned, and only a genuinely quiet holder is
flagged -- as "confirm before releasing", not "release it".

The catch block sets an explicit "liveness check FAILED" annotation
rather than leaving it blank. An empty annotation reads as "nothing
notable about this claim", which is the same silent-instrument failure
the age signal had: accurate about what it measured, mute about what it
could not.

Verified: live holder, missing worktree, and non-git-directory paths all
render correctly; -Take and -Release unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@wshallwshall
wshallwshall merged commit 599f9e2 into main Aug 2, 2026
33 checks passed
@wshallwshall
wshallwshall deleted the claim-liveness branch August 2, 2026 04:36
wshallwshall added a commit that referenced this pull request Aug 2, 2026
…had looked at (BACKLOG #345 Half B) (#151)

#106 taught `-List` to probe whether a claim's holder still exists on disk. That was the wrong half
to fix alone: `-List` is where you BROWSE, and `-Take`/`-Release` are where a session is STOPPED and
has to choose between waiting, picking other work, and taking the key. Both blocking paths printed
the same "held by another worktree" block whether the holder had been deleted, had died, or was
committing that minute -- and `-Release` went further, printing "If that session is gone, re-run
with -Force" unconditionally, about a holder it had never probed.

That is an instruction to guess, issued at the exact moment the guess is expensive: the cheapest way
past the gate was also the one that frees a live session's key. This file's own header records what
that costs -- three sessions fixed the same npm advisory on 2026-07-24, two PRs closed as duplicates,
and the survivor had not tested what the others found.

All three surfaces now share ONE helper, because they had been disagreeing: -List probed, the other
two did not probe at all. `-Take` blocked by a vanished holder names the exact take-over commands;
blocked by a living one it withholds the -Force recipe entirely and says quiet is not dead.

THE ASYMMETRY IS THE DESIGN. A vanished worktree is a fact and the one state safe to act on unasked.
Present, undatable and unprobeable all read as "coordinate first", never "probably fine" -- a session
can be alive and simply not committing, so silence is not evidence of death. A probe hardwired to
'gone' would pass every positive assertion, so each is paired with the negative case that catches it.
The catch returns 'failed', never 'gone': an unreadable path must not become a licence to release
someone's live claim.

-Force is deliberately untouched. This reports; it does not enforce. Refusing to override a live
claim would strand every key whose holder is merely unreachable -- the same orphan, one level up.

Verified: 4 of the 9 new tests FAIL against main's claim.ps1 and the other 5 are regression guards
that must pass on both sides. One assertion was rewritten mid-build after it failed on the PATCHED
code -- it searched for the token `-Force`, which the live-holder text contains inside "do NOT -Force
it"; the property wanted is "no runnable recipe", so it now asserts the absence of the command line.
Re-checked against unpatched afterwards to prove the rewrite had not made it vacuous. 41 coordination
tests green.
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