Conversation
XERK-224 demoted a session out of Ready-for-review once every PR it opened
reached MERGED/CLOSED — merging IS the review, so it drops to Idle to be parked
until the build is verified. But that demotion was ABSOLUTE, and a session is a
conversation, not a pull request: give the same session a new task after the
merge and it finishes work with no new PR to show for it, and the landed one
hid that result for good. The rule never even reached the finished-turn check:
if (prs.length) return prs.some(p => !prLanded(p)); // early return
So the demotion is now scoped in TIME. `_poll_prs_landed` runs beside
`_poll_pr_conflicts`, off the status the PR sweep just cached (no extra network
call), and stamps `prsLandedTs` — the session's own last-activity timestamp at
the moment every PR it owns is seen landed. `_new_work_since_prs` compares that
against the session's current last activity and reports `newWorkSincePrs`;
`readyForReview` falls through to the finished-turn signal once the
conversation has moved past the landing. A newly opened PR clears the stamp so
the next landing is measured fresh, and an unfetched PR state stamps nothing —
"not looked at" is not "landed", and stamping early would measure new work
against the wrong moment.
Both sides of that comparison are transcript ENTRY timestamps, so it runs on
the conversation's own clock: never wall time, and never file mtime, which a
synced ~/.claude or a backup restore inflates to copy-time (the trap XERK-73
already documents). The answer is False whenever it can't be known — no stamp,
an undated transcript, an agent too old to report it — which is exactly the
behaviour that shipped before this expiry, so a lagging host degrades rather
than flips.
Mirrored in all four places the rule lives (sessions.html, server.js,
core/Sessions.kt, glasses/src/sessions.ts), so the hub's one ready-for-review
alert fires for the follow-up too — and its body says "nothing to merge" rather
than naming the PR that already landed.
Verified: 1005 agent tests (5 new, TestPrsLanded), turma 14/14 files (278
server, 68 sessions), glasses 463 + typecheck, Android 226 tests compiled and
run in the agent image, and the browser E2E 23/23 against a real hub — the last
three checks walk the reported lifecycle: merged and quiet parks in Idle, a new
task surfaces under Ready for review, and it reads as a finished turn rather
than as the old PR.
Also corrects a test whose NAME claimed this behaviour ("a merged PR still
comes back once the session runs again") while asserting the opposite.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #396. Fixes a real hole in the Ready-for-review rule that shipped there.
The bug
XERK-224 demoted a session out of Ready-for-review once every PR it opened reached MERGED/CLOSED — merging is the review, so it drops to Idle to be parked until the build is verified. But that demotion was absolute, and a session is a conversation, not a pull request.
Give the same session a new task after the merge, and it finishes work with no new PR to show for it. The landed PR hid that result permanently — the rule never even reached the finished-turn check:
The fix
The demotion is now scoped in time.
_poll_prs_landedruns beside_poll_pr_conflicts, off the status the PR sweep just cached (no extra network call), and stampsprsLandedTs— the session's own last-activity timestamp at the moment every PR it owns is seen landed._new_work_since_prscompares that against the session's current last activity and reportsnewWorkSincePrs;readyForReviewfalls through to the finished-turn signal once the conversation has moved past the landing.Three details worth keeping:
~/.claudeor a backup restore inflates to copy-time (the trap XERK-73 already documents).The answer is
falsewhenever it can't be known (no stamp, undated transcript, an agent too old to report it), which is exactly the behaviour that shipped before this expiry — so a lagging host degrades rather than flips.Mirrored in all four places the rule lives (
sessions.html,server.js,core/Sessions.kt,glasses/src/sessions.ts), so the hub's one ready-for-review alert fires for the follow-up too — with a body that says "nothing to merge" rather than naming the PR that already landed.Verification
TestPrsLandedThe last three browser checks walk the reported lifecycle end to end:
The hub alert timeline was exercised through the real
heartbeatAlertswith a captured FCM sender: PR announced → merged → alert retracted → new task → announced again, without naming the merged PR.Two things worth flagging
"a merged PR still comes back once the session runs again"asserted the session stayed in Idle, and its comment described behaviour the code didn't have. Rewritten to test what its name claims. Compiling the Android side also caught a genuine error in my new Kotlin test (JUnit'sassertEqualstakes the message first) — worth noting sinceandroid-cinever ran on Add a "Ready for review" session group, with one alert (XERK-224) #396 due to the Actions outage; I ran it locally in the agent image.CLAUDE.mdremains jammed at its cap. This change needed ~850 characters of new documentation, so it took another consolidation pass to land at 149,991 of 150,000. That's now the fourth PR in a row paying this tax.