Skip to content

Scope the Stop-hook fix loop to the session that made the commit - #1

Merged
andreidavid merged 1 commit into
andreidavid:mainfrom
martingtobias:session-scoped-fix-loop
Jul 24, 2026
Merged

Scope the Stop-hook fix loop to the session that made the commit#1
andreidavid merged 1 commit into
andreidavid:mainfrom
martingtobias:session-scoped-fix-loop

Conversation

@martingtobias

Copy link
Copy Markdown
Contributor

Problem

With two Claude Code sessions open in the same repo, a FAIL review triggered by session A also blocks session B on Stop. B receives:

Fix the issues identified in the review and commit them -- amend the reviewed commit if it has not been pushed…

…for a commit it did not make. Meanwhile session A is very likely still working those same files with uncommitted changes. Following the instruction means editing a file another agent holds live edits in, and --amend-ing that agent's commit out from under it.

I hit this for real: a second session was told three times to amend another running session's unpushed commit, with that session's server/routes.ts dirty at the time.

Fix

post-commit-review.sh runs as a PostToolUse hook, so it already receives the harness JSON and knows which session made the commit. Record that session_id as a fourth field on the FAIL state; the Stop hook then blocks only when the state is unowned or owned by the current session.

Two details that matter:

  • A non-owning session stays completely inert — no block, and it does not touch the state file or the loop counter, both of which the owning session is still using. Clearing either would silently reset the owner's fix loop.
  • Ownership is checked after the existing staleness rules, so an abandoned session's state still self-clears for anyone via the one-hour cap. Ownership can't wedge state.

Compatibility

State written before this change — or by a commit made outside a Claude session — carries no session id. That disables the check and preserves current single-session behaviour exactly. Same when the harness supplies no session_id.

Tests

tests/helpers.bash gains an optional trailing session_id argument on hook_input and stop_input; omitted means the key is absent from the JSON, so every existing test exercises the unstamped path unchanged.

Seven new cases:

  • FAIL owned by another session: no block, owner's state and counter untouched
  • FAIL owned by this session: still blocks and increments
  • session-stamped FAIL blocks a session that reports no id
  • unstamped FAIL blocks any session (pre-upgrade state)
  • stale FAIL owned by another session is still cleared
  • FAIL state records the session that made the commit
  • FAIL state omits the session field when the harness supplies none

bats tests/ — 56/56 pass (1 pre-existing skip: coreutils timeout not available on this machine).

With two Claude sessions open in one repo, a FAIL review by session A also
blocks session B on Stop. B is then told to "amend the reviewed commit" --
a commit it did not make, while A is very likely still editing those files
with uncommitted changes. Following that instruction clobbers live work.

post-commit-review.sh runs as a PostToolUse hook, so it already receives the
harness JSON and knows which session made the commit. Record that session id
as a fourth field on the FAIL state, and have the Stop hook block only when
the state is unowned or owned by the current session.

A non-owning session stays completely inert: it does not block, and it does
not touch the state file or the loop counter, both of which the owning
session is still using. Ownership is checked after the existing staleness
rules, so an abandoned session's state still self-clears for anyone via the
one-hour cap.

Backward compatible: state written before this change (or by a commit made
outside a Claude session) carries no session id, which disables the check
and preserves current behaviour exactly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@andreidavid andreidavid left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Exactly the fix this needed. The details are all right: ownership checked after the staleness rules so state can't wedge, the state format compatible in both directions, and a non-owning session staying fully inert instead of touching the owner's counter. The test coverage is exemplary — every edge case I went looking for was already there, including "non-owner leaves the counter untouched" and "unstamped pre-upgrade state still blocks". Thanks for the careful diagnosis and the writeup; this was a pleasure to review. Merging.

@andreidavid
andreidavid merged commit 42d0009 into andreidavid:main Jul 24, 2026
2 checks passed
andreidavid added a commit that referenced this pull request Jul 24, 2026
…NDS)

Conflict with #1 resolved as predicted in the PR: the round-cap
restructure of the FAIL branch keeps the session-id field on the
blocking arm's state write, so both features compose.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
andreidavid added a commit that referenced this pull request Jul 24, 2026
Round-counter staleness: the counter is now stamped with the epoch of
the last FAIL ("<count> <epoch>"); a stamp older than an hour -- or a
bare pre-stamp count, or a stamp from the future (clock stepped
backward) -- reads as a fresh saga. Without this, a count abandoned
mid-saga (interrupt, kill switch, manual fix outside a session) leaked
into unrelated future FAILs and tripped the cap early.

History entries gain "capped": true on a FAIL demoted to advisory at
the round cap, so it can be told apart from a FAIL that blocked. The
cap message reports the actual round number instead of assuming it
equals the cap.

README catches up with #1 and #2: CODEX_REVIEW_MAX_ROUNDS in the
config table, session-scoped fix loop, the round-count state file and
its staleness rule, and a cost section that states the real per-saga
bound. CHANGELOG for 1.6.0 credits both contributions.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.

2 participants