fix(heartbeat): guard finalizeAgentStatus against clobbering a concurrent pause/terminate (RBR-932) - #11064
fix(heartbeat): guard finalizeAgentStatus against clobbering a concurrent pause/terminate (RBR-932)#11064PraeSynBH wants to merge 1 commit into
Conversation
|
✅ All checks passing — ready for Greptile review and maintainer approval. — commitperclip |
Greptile SummaryThe PR makes the agent-status finalization write atomic with concurrent pause and termination operations.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| server/src/services/heartbeat.ts | Adds the paused/terminated compare-and-set predicate to the production finalization update without changing normal finalization behavior. |
| server/src/tests/heartbeat-finalize-agent-status-pause-race.test.ts | Exercises the production finalization path and deterministically injects pause or termination immediately before the guarded update executes. |
Reviews (2): Last reviewed commit: "fix(heartbeat): guard finalizeAgentStatu..." | Re-trigger Greptile
|
|
||
| if (!embeddedPostgresSupport.supported) { | ||
| console.warn( | ||
| `Skipping embedded Postgres agent-status CAS tests on this host: ${embeddedPostgresSupport.reason ?? "unsupported environment"}`, |
There was a problem hiding this comment.
PR description omits required sections
The PR description includes the problem, fix, and testing details, but it omits the required top-down Thinking Path and explicit Risks section. Please update it to follow the template required by CONTRIBUTING.md so reviewers have the project-level rationale, benefits, and risk assessment.
Context Used: CONTRIBUTING.md has a guide for a good PR message ... (source)
Prompt To Fix With AI
This is a comment left during a code review.
Path: server/src/__tests__/heartbeat-agent-status-cas.test.ts
Line: 19
Comment:
**PR description omits required sections**
The PR description includes the problem, fix, and testing details, but it omits the required top-down Thinking Path and explicit Risks section. Please update it to follow the template required by `CONTRIBUTING.md` so reviewers have the project-level rationale, benefits, and risk assessment.
**Context Used:** CONTRIBUTING.md has a guide for a good PR message ... ([source](https://app.greptile.com/review/custom-context?memory=instruction-0))
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
…rent pause/terminate (RBR-932)
finalizeAgentStatus read the agent, checked paused/terminated in JS
against that snapshot, then awaited countRunningRunsForAgent, then wrote
status back with an unguarded where(eq(agents.id, agentId)). An operator
pause (or terminate) landing in that window was silently clobbered back
to idle/running/error.
Move the guard into the WHERE clause of the write itself, matching the
already-correct finalizeAgentAfterSourceResolvedRun in
recovery/service.ts:
.where(and(eq(agents.id, agentId),
notInArray(agents.status, ["paused", "terminated"])))
The JS check stays as a cheap early-out; it is documented as
non-authoritative. The new regression test drives the real production
path (heartbeatService(...).cancelRun() -> finalizeAgentStatus) with an
injected race that commits the operator's pause/terminate write between
the service's snapshot read and its own finalize write, then asserts the
final persisted agent status -- not call shapes -- per AC3. A control
case without the race still finalizes to idle, and an already-paused
agent stays paused via the cheap JS early-out.
Refs: RBR-932, RBR-923 AC6
7632bae to
adc5ecf
Compare
Thinking Path
Linked Issues or Issue Description
No public GitHub issue exists for this internally-tracked defect. Following the Bug report template:
finalizeAgentStatusreads an agent's status, performs an early-out JS check forpaused/terminated, then several awaits later writes the agent's status with a WHERE clause scoped only toagents.id. If an operator pauses or terminates the agent in that window, the write silently overwrites their action back toidle/running/error.server/src/services/heartbeat.ts,finalizeAgentStatus.CONFLICTING/stale against current master, and is from an external contributor fork. This PR is a fresh, mergeable, currently-passing-tests implementation of the same fix rebased on current master, with a regression test that drives the real production call path. Recommend closing fix(heartbeat): make agent pause status guard atomic with status update #4503 as superseded once this lands (or vice versa, whichever reviewers prefer — the code intent is identical).What Changed
server/src/services/heartbeat.ts:finalizeAgentStatus's UPDATE now carriesnotInArray(agents.status, ["paused", "terminated"])in its WHERE clause alongsideeq(agents.id, agentId), making the guard atomic with the write. The existing JS-level early-out check is kept as a cheap early exit and commented as non-authoritative.server/src/__tests__/heartbeat-finalize-agent-status-pause-race.test.ts(new): 4 embedded-Postgres regression tests that drive the real production path —heartbeatService(db).cancelRun(runId)→finalizeAgentStatus— with an injected race (adb.updateproxy that commits the operator's pause/terminate write between the service's snapshot read and its own finalize write). Asserts final persistedagents.status, not call shapes:idle(control)Verification
npx tsc --noEmit -p .shows zero new errors from this change (pre-existing, unrelated@paperclipai/plugin-sdkmodule-resolution errors in other files are untouched — confirmed zero hits forheartbeat.tsor the new test file in the tsc output).Risks
Low. This makes an existing early-return guard atomic with its own write; it does not change behavior for any caller that wasn't already relying on the (incorrect) racy overwrite.
recovery/service.tsalready uses the identical predicate for the analogous write, so this bringsfinalizeAgentStatusin line with established precedent in the same codebase rather than introducing a new pattern.Model Used
Checklist