Skip to content

fix: drop heartbeat runs that arrive while another run is active#12786

Closed
mcaxtr wants to merge 3 commits intoopenclaw:mainfrom
mcaxtr:fix/8063-heartbeat-followup-branches
Closed

fix: drop heartbeat runs that arrive while another run is active#12786
mcaxtr wants to merge 3 commits intoopenclaw:mainfrom
mcaxtr:fix/8063-heartbeat-followup-branches

Conversation

@mcaxtr
Copy link
Contributor

@mcaxtr mcaxtr commented Feb 9, 2026

Summary

Fixes #8063

When a heartbeat run arrives while another agent run is already active for the same session, it was being enqueued as a followup. When the followup queue later drained, the stale heartbeat produced a duplicate agent run — sending extra response branches to the user.

This change adds an early-return guard in runReplyAgent that silently drops heartbeat runs when isActive is true, before they reach the enqueue path. The next heartbeat interval will independently re-check the session.

  • Non-heartbeat runs are unaffected — they continue to enqueue normally
  • The followup drain mechanism remains fully intact for legitimate queued messages
  • Heartbeat runs that arrive when no other run is active execute normally

Test plan

  • New test: heartbeat run is dropped (returns undefined, no enqueue) when isActive: true
  • New test: non-heartbeat run still enqueues when isActive: true
  • New test: heartbeat run executes normally when isActive: false
  • All 3 new tests fail before the fix, pass after (TDD)
  • Existing agent-runner test suites pass (13 test files)
  • Existing heartbeat-runner test suites pass (43 tests)
  • pnpm build && pnpm check pass

Greptile Overview

Greptile Summary

This change adds a guard in src/auto-reply/reply/agent-runner.ts to silently drop heartbeat-triggered runs when another run is already active for the same session, preventing stale heartbeat followups from being enqueued and later producing duplicate agent runs/extra response branches (fix for #8063). It also adds a focused vitest suite that asserts: (1) heartbeat+active returns undefined and does not enqueue, (2) non-heartbeat+active still enqueues, and (3) heartbeat+inactive proceeds to execute normally.

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk.
  • The change is narrowly scoped (an early-return guard for a specific heartbeat+active condition) and is covered by new targeted tests that validate both the new behavior and non-regression for non-heartbeat runs. The previously reported run-completion bookkeeping gap on the early-return path is addressed in the current diff via explicit typing.markRunComplete() calls.
  • No files require special attention

@mcaxtr
Copy link
Contributor Author

mcaxtr commented Feb 9, 2026

@greptile review

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@mcaxtr
Copy link
Contributor Author

mcaxtr commented Feb 9, 2026

@greptile review

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 9, 2026

Additional Comments (1)

src/auto-reply/reply/agent-runner.ts
Inconsistent run completion

In steered && !shouldFollowup early-return you call typing.cleanup() and return, but never call typing.markRunComplete(). The new heartbeat drop path explicitly calls markRunComplete() before cleanup, and the function’s normal exits rely on the finally to do this. If markRunComplete() is required to release typing/run bookkeeping, this branch can leave the controller in an inconsistent state.

This can be fixed by ensuring the steered early-return triggers the same completion signal as other exits (e.g. call typing.markRunComplete() before cleanup(), or structure it so it flows through the finally).

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/auto-reply/reply/agent-runner.ts
Line: 162:179

Comment:
**Inconsistent run completion**

In `steered && !shouldFollowup` early-return you call `typing.cleanup()` and return, but never call `typing.markRunComplete()`. The new heartbeat drop path explicitly calls `markRunComplete()` before cleanup, and the function’s normal exits rely on the `finally` to do this. If `markRunComplete()` is required to release typing/run bookkeeping, this branch can leave the controller in an inconsistent state.

This can be fixed by ensuring the steered early-return triggers the same completion signal as other exits (e.g. call `typing.markRunComplete()` before `cleanup()`, or structure it so it flows through the `finally`).

How can I resolve this? If you propose a fix, please make it concise.

@mcaxtr
Copy link
Contributor Author

mcaxtr commented Feb 9, 2026

@greptile review

@mcaxtr mcaxtr force-pushed the fix/8063-heartbeat-followup-branches branch 3 times, most recently from 0a44471 to f907bd6 Compare February 12, 2026 04:14
@mcaxtr mcaxtr force-pushed the fix/8063-heartbeat-followup-branches branch from f907bd6 to 4c0e258 Compare February 13, 2026 02:30
@mcaxtr mcaxtr force-pushed the fix/8063-heartbeat-followup-branches branch from 4c0e258 to 2fe4c3c Compare February 13, 2026 14:45
@mcaxtr mcaxtr force-pushed the fix/8063-heartbeat-followup-branches branch 8 times, most recently from 9b29efd to 31d9a86 Compare February 15, 2026 15:09
@mcaxtr mcaxtr force-pushed the fix/8063-heartbeat-followup-branches branch 2 times, most recently from 9336c84 to 6d89a29 Compare February 17, 2026 01:28
@mcaxtr mcaxtr force-pushed the fix/8063-heartbeat-followup-branches branch from 6d89a29 to c054172 Compare February 19, 2026 02:05
@steipete
Copy link
Contributor

Closing as AI-assisted stale-fix triage.

Linked issue #8063 ("[Bug]: Heartbeat sends multiple response branches due to followup-runner and delivery-mirror") is currently CLOSED and was closed on 2026-02-24T04:26:28Z with state reason NOT_PLANNED.
Given that issue state, this fix PR is no longer needed in the active queue and is being closed as stale.

If the underlying bug is still reproducible on current main, please reopen this PR (or open a new focused fix PR) and reference both #8063 and #12786 for fast re-triage.

@steipete
Copy link
Contributor

Closed after AI-assisted stale-fix triage (closed issue duplicate/stale fix).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Heartbeat sends multiple response branches due to followup-runner and delivery-mirror

2 participants