fix(heartbeat): correct the exclusivity mechanism comment, link BLO-31403 (BLO-31282) - #1626
fix(heartbeat): correct the exclusivity mechanism comment, link BLO-31403 (BLO-31282)#1626allyblockcast[bot] wants to merge 2 commits into
Conversation
…1403 (BLO-31282) Review follow-ups from the CTO ruling on BLO-31282. These were written but stranded uncommitted when the run that authored them died on an adapter 403, so #1610 squash-merged (f0d69fe) without them. 1. The permanent comment stated that `executionRunClaimCondition` "is skipped entirely when `allowsIssueInteractionWake` holds". That is false and points at the exact guard a verifier checks first, so it would read as falsifying the whole narrowing caveat. Verified at this head rather than taken on trust: the condition is built at `heartbeat.ts:19727` with no wake-reason branch (its only branch is the retry-lock one) and is unconditionally present in the claiming UPDATE's `.where(...)` at `:19825`. `allowsIssueInteractionWake` gates `issueLockRequired` (`:19725`), consumed only by the post-UPDATE guard at `:19852`. So the claim condition is still applied; an interaction wake tolerates it matching nothing and proceeds without holding the issue lock. Cited by symbol rather than line number so the comment does not rot. 2. Record why no lock-ordering or retry fix exists: the second run never acquires `issues.executionRunId` at all, so it is a deliberately lock-less run rather than a competing lock holder. Also note that `maxConcurrentRuns: 1` narrows the window without closing it, since a run silent past the BLO-12990 floor leaves the non-stale tally while still holding the worktree. 3. Replace "tracked separately" with BLO-31403, which now exists. An invariant gap carried in a comment with no findable id is the same failure mode the BLO-31281/31340/31349 family is made of. Also pins that the worktree change did not widen into the other three storage classes: home/session/cache stay `ephemeral` under `run` isolation. Verification: heartbeat-workspace-session.test.ts 235/235 passed; `tsc --noEmit` exit 0. Co-Authored-By: Paperclip <noreply@paperclip.ing>
1 similar comment
|
Closing as a duplicate of #1623, which is the same three BLO-31282 review follow-ups and a strict superset of this PR. I opened this without seeing #1623 — it was created at 05:48:43Z, ~9 minutes before this one, and I checked the issue thread rather than the open-PR list. My fault, and a small live instance of the very thing BLO-31282 is about: two lanes working one issue with no exclusivity between them. #1623 is better on the merits, so there is nothing to port:
One delta, already captured elsewhere and deliberately not worth a commit: this PR also noted that No work is lost. |
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 3d21658
Looks good. This is a comment-only production change plus three added test assertions — zero runtime behavior changes. Since the entire value of the PR is the accuracy of the mechanism description, I verified each asserted claim against heartbeat.ts at this head rather than reading the prose alone. All of them hold:
| Claim in the new comment | Verified at head |
|---|---|
executionRunClaimCondition is built unconditionally, no wake-reason branch |
✅ :19727 — branches only on requiresIssueExecutionRetryLock, always assigned |
always present in the .where(...) of the claiming UPDATE |
✅ :19825 |
allowsIssueInteractionWake gates issueLockRequired, not the claim condition |
✅ :19725 |
issueLockRequired consumed only at the post-UPDATE check |
✅ :19852, if (issueLockRequired && !claimedIssueLock) |
| interaction wake falls straight through that guard | ✅ falls to return claimed |
availableSlots computed from non-stale running runs |
✅ :24571 runningCount = nonStaleRunningRuns.length |
| BLO-31403 is the tracking ticket for the path-keyed repair | ✅ exists, todo/high, AC matches this comment's "principled repair" text |
The old comment's claim that the lock "is skipped entirely when allowsIssueInteractionWake holds" was genuinely wrong — the UPDATE always carries the guard, it just tolerates matching zero rows. Correcting that is worth the diff on its own, and the added "no configuration in which this closes" conclusion is the load-bearing part: it's what stops a future reader from attempting a lock-ordering or retry fix that cannot work.
Critical Issues (0)
Important Issues (0)
Suggestions (3)
- [native-codex]
server/src/services/heartbeat.ts:6367— "It is a deliberately lock-less run" slightly overstates it. The run is lock-tolerant, not lock-less: whenissues.executionRunId IS NULLthe UPDATE matches and the interaction wake does acquire the lock. The blanket "the second run never ACQUIRESissues.executionRunIdat all" on:6366is true only in the contended case actually under discussion, so it reads correctly in context — but in a comment whose whole purpose is mechanical precision, "acquires the lock when free, and proceeds without it when contended" would be exactly right and no longer. - [native-codex]
server/src/services/heartbeat.ts:6357— the predicate is rendered asexecutionRunId IS NULL OR = <claiming run>, but underrequiresIssueExecutionRetryLock(claimedRetryReason) && claimed.retryOfRunIdthere is a third disjunct,eq(issues.executionRunId, claimed.retryOfRunId). Carried over unchanged from the prior text and immaterial to the argument being made, so entirely optional — flagging only because this hunk is where the predicate is now being quoted as authoritative. - [pr-review-toolkit/tests]
server/src/__tests__/heartbeat-workspace-session.test.ts:3142— the three new assertions are correct and will pass (isolationMode: "run"⟹persistent = "ephemeral"for home/session;cacheis"ephemeral"for any non-sharedmode). The sibling test at:3103pins the same thing with a singleexpect(isolation?.storage).toEqual({...}), which is strictly stronger — it also fails if a new storage class is added and left unpinned. Matching that form here would cost nothing and close the same gap on the shipping path. Note also thatcache: "ephemeral"is the weakest of the three, since it holds forworkspaceisolation too; onlyhome/sessiongenuinely discriminaterunmode.
Strengths
- Correcting a wrong mechanism claim in a comment is easy to skip and easy to get wrong; this one names the exact symbols (
executionRunClaimCondition,issueLockRequired,claimedIssueLock) so the next reader can re-verify in one grep instead of re-deriving the control flow. - The
maxConcurrentRuns: 1paragraph pre-empts the obvious "just set concurrency to 1" response with the specific reason it fails (BLO-12990 staleness dropping a live run from the tally). That is the non-obvious half and it would otherwise have been rediscovered the hard way. - Replacing "tracked separately" with the concrete
BLO-31403closes a dangling reference — the CTO's own AC on that ticket calls out keeping the invariant gap from living only in a code comment, and this satisfies it from the code side. - The added assertions pin the negative space (worktree persistence did not widen into the other storage classes), which is the failure mode a future change would actually introduce.
Recommended Action
- No Critical or Important issues — nothing blocking.
- Suggestions are all optional polish; the first is the only one with any reader-facing value and is a one-line rewording.
Posted as a formal COMMENTED review: this PR is authored by the Ally App, which GitHub bars from approving its own pull request. reviewDecision is empty on this PR, so no required-review gate is outstanding.
Thinking Path
Linked Issues or Issue Description
Refs #1610 — these are its review follow-ups, which were stranded uncommitted when the authoring run died on an adapter
403and #1610 squash-merged (f0d69fef) without them.Refs BLO-31282 (the routing bug), BLO-31403 (the path-keyed reservation repair now referenced in the comment).
What Changed
heartbeat.ts:6355-6364). It previously saidexecutionRunClaimCondition"is skipped entirely whenallowsIssueInteractionWakeholds". It is never skipped. It is built with no wake-reason branch and is unconditionally present in the claiming UPDATE's.where(...). WhatallowsIssueInteractionWakegates isissueLockRequired, consumed only by the post-UPDATE guard — so the condition is applied, matches zero rows, and an interaction wake tolerates that and proceeds without the issue lock.issues.executionRunIdat all, so it is a deliberately lock-less run rather than a competing lock holder. There is no lock-ordering or retry repair and no configuration in which the race closes.maxConcurrentRuns: 1narrows but does not close the window — a run silent past the BLO-12990 floor leaves the non-stale tally while still alive and still holding the worktree, freeing a slot.BLO-31403, which now exists.home/session/cachestayephemeralunderrunisolation.Comment-and-test only. No behavioral change.
Verification
I verified the corrected mechanism against this head rather than taking the review on trust:
issueLockRequiredis what the wake gatesheartbeat.ts:19725—const issueLockRequired = !allowsIssueInteractionWake(claimedContext);:19727— its only branch isrequiresIssueExecutionRetryLock, and both arms return a condition:19825— inside theand(...)of.where(...):19852—if (issueLockRequired && !claimedIssueLock)is the only consumer; an interaction wake falls through toreturn claimedRisks
Low risk. The only executable change is three added assertions in an existing test, which pass and are non-vacuous (
expect(undefined).toBe("ephemeral")would fail). Everything else is a comment.The underlying exclusivity gap is not fixed here and is not claimed to be — it is deliberately deferred to BLO-31403, which this PR exists partly to make findable.
review/ally-commentmay stay red for that reason; it is advisory by configuration and not inverify'sneeds.Model Used
Claude Opus 4.5 (
claude-opus-4-5), extended thinking, via Claude Code with tool use and code execution.Checklist
Fixes: #/Closes #/Refs #OR (b) described the issue in-PR following the relevant issue templatereview/ally-commentis expected red per above