Skip to content

fix: heartbeat is_current_turn false after steer via command() - #5820

Closed
Christian-Sidak wants to merge 1 commit into
Agenta-AI:mainfrom
Christian-Sidak:fix/issue-5790
Closed

fix: heartbeat is_current_turn false after steer via command()#5820
Christian-Sidak wants to merge 1 commit into
Agenta-AI:mainfrom
Christian-Sidak:fix/issue-5790

Conversation

@Christian-Sidak

@Christian-Sidak Christian-Sidak commented Aug 9, 2026

Copy link
Copy Markdown

Summary

After a steer (interrupt + new message on a session), the displaced turn's heartbeat kept reporting is_current_turn: true. Clients polling the old turn had no signal that it had been superseded.

Root cause. heartbeat() used the durable row's turn_id to disambiguate a lock loss: if prior_stream.turn_id == request.turn_id the key being gone meant cancellation; if not, it looked like a first-beat race and was left as is_current_turn=True. A steer calls _start_turn(), which overwrites the durable row's turn_id with the new turn before the old turn's next heartbeat fires. So the old turn's beat always saw a "different" turn_id in the row, concluded "first-beat race", and stayed True -- even though it had been displaced.

Fix. When refresh_alive fails, read the alive lock's current holder directly via get_alive_owner. If the lock is held by a different turn_id, the old turn was displaced by a steer (is_current_turn=False). The durable row check (turn_was_established) is kept as the fallback for the cancel case (lock absent, row still matches our turn).

Files changed:

  • api/oss/src/core/sessions/streams/service.py -- import get_alive_owner, fix is_current_turn logic in heartbeat()
  • api/oss/tests/pytest/unit/sessions/test_heartbeat_is_current_turn.py -- regression test that goes through command() write path

Fixes #5790

Testing

Verified locally

  • Ran api/oss/tests/pytest/unit/sessions/test_heartbeat_is_current_turn.py (all 5 tests pass)
  • Ran related session unit tests (33 tests pass): test_command_matrix_inputs_data.py, test_project_scoped_locks.py, test_owner_claim.py, test_heartbeat_ownership.py, test_heartbeat_first_touch_race.py
  • ruff format and ruff check pass on changed files

Added or updated tests

Added test_steer_via_command_flips_old_turn_heartbeat_to_not_current to test_heartbeat_is_current_turn.py. This test drives the real steer write path via command(force=True) -- the exact sequence from the bug report -- rather than manually simulating a lock cancellation. It verifies that after a steer via the command endpoint, the old turn's heartbeat reports is_current_turn=False.

QA follow-up

N/A -- pure coordination-plane logic fix, no user-visible UI or API shape change.

Demo

This is a pure backend coordination-plane fix -- no UI change, no API shape change. The observable behavior change is a correctness fix in a boolean field on an internal heartbeat response.

Test run demonstrating the fix (all 5 tests pass, including the new regression test):

PASSED test_uninterrupted_heartbeats_stay_current
PASSED test_cancel_between_beats_flips_next_beat_to_not_current
PASSED test_steer_flips_the_old_turns_next_beat_to_not_current
PASSED test_losing_owner_claim_reports_not_current
PASSED test_steer_via_command_flips_old_turn_heartbeat_to_not_current  <- new regression test
5 passed in 1.84s

The new test would fail on main (before this fix) because the old code's durable-row comparison cannot detect the steer displacement.

Checklist

  • I have included a video or screen recording for UI changes, or marked Demo as N/A
  • Relevant tests pass locally
  • Relevant linting and formatting pass locally
  • I have signed the CLA, or I will sign it when the bot prompts me

When a steer happens, `_start_turn()` overwrites the durable row's
`turn_id` with the new turn before the old turn's next heartbeat fires.
The original code compared the durable row's `turn_id` to decide if a
lock loss was a cancel vs. a first-beat race. For the steer case, the
row already shows the new turn_id, so `turn_was_established` is False
and `is_current_turn` stays True -- the old turn thinks it is still
current even though it has been displaced.

Fix: when `refresh_alive` fails, read the alive lock's current holder
directly. If the lock is held by a different turn_id, the old turn was
displaced by a steer and `is_current_turn` is set to False. The durable
row check (`turn_was_established`) is kept as the fallback for the
cancel case (lock absent, row matches).

Adds a regression test that drives the real steer write path via
`command()` with `force=True` rather than manually simulating the lock
cancellation, matching the exact sequence from issue Agenta-AI#5790.

Fixes Agenta-AI#5790

Signed-off-by: Christian-Sidak <61099993+Christian-Sidak@users.noreply.github.com>
@vercel

vercel Bot commented Aug 9, 2026

Copy link
Copy Markdown

@Christian-Sidak is attempting to deploy a commit to the agenta projects Team on Vercel.

A member of the Team first needs to authorize it.

@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. python Pull requests that update Python code tests labels Aug 9, 2026
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Hi @Christian-Sidak, thanks for opening a pull request. 🙏

This PR was automatically closed because it does not yet meet our contribution requirements:

  • This PR changes functional code (SDK, API, or frontend) but includes no demo. Add a screenshot or short video of the change. Only test-only, docs-only, or chore changes may skip it.

We ask for this so every change is documented and demonstrably tested before review.

How to get it reopened
Update the PR description (and add a demo recording if your change touches functional code). The bot reopens the PR automatically once the requirements are met. No need to open a new one.

See the Contributing guide and Creating your first PR. If you think this was closed in error, leave a comment and a maintainer will take a look.

@CLAassistant

CLAassistant commented Aug 9, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5bda64c4-68e5-4a31-9954-085fb8eb7211

📥 Commits

Reviewing files that changed from the base of the PR and between e73fb2e and 0cd4d02.

📒 Files selected for processing (2)
  • api/oss/src/core/sessions/streams/service.py
  • api/oss/tests/pytest/unit/sessions/test_heartbeat_is_current_turn.py

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.


📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Improved heartbeat interruption detection when a session turn is replaced or its lock is cancelled.
    • Ensured older turns correctly report that they are no longer active after a command redirects the session.
  • Tests

    • Added regression coverage for turn-steering commands and heartbeat status updates.

Walkthrough

Heartbeat interruption detection now checks the Redis alive-lock owner before using the durable turn row. A regression test exercises steering through command(force=True) and verifies that the displaced turn reports is_current_turn=False.

Changes

Heartbeat current-turn detection

Layer / File(s) Summary
Heartbeat lock-owner detection
api/oss/src/core/sessions/streams/service.py
Heartbeat processing uses the Redis alive-lock owner to distinguish turn displacement from initial lock establishment and marks displaced turns as stale.
Command steering regression coverage
api/oss/tests/pytest/unit/sessions/test_heartbeat_is_current_turn.py
The test starts a turn, steers through command(force=True), and verifies that the original turn reports is_current_turn=False while a replacement turn exists.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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

Labels

python Pull requests that update Python code size:M This PR changes 30-99 lines, ignoring generated files. tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

(bug) Steered turn's heartbeat still reports is_current_turn: true after being displaced

2 participants