Skip to content

fix: release conversation subject in cancel terminal update - #490

Open
onatozmenn wants to merge 8 commits into
OpenHands:mainfrom
onatozmenn:fix/release-subject-on-cancel
Open

onatozmenn wants to merge 8 commits into
OpenHands:mainfrom
onatozmenn:fix/release-subject-on-cancel

Conversation

@onatozmenn

@onatozmenn onatozmenn commented Sep 18, 2026 •

Copy link
Copy Markdown

HUMAN:

I ran the targeted pytest suite and confirmed all 109 tests pass, including the two new subject-release cases that fail on base and pass with the fix, with ruff checks also clean.


Why

Cancelling a run that holds a conversation subject only released it when the run had recorded a sandbox_id (and in a second write after the terminal update). Local-mode and conversation-turn child runs often have no sandbox_id, so subject_released_at stayed unset and a resubmitted subject kept deduplicating against the cancelled run instead of creating a replacement (#472).

Summary

  • Record subject_released_at atomically in the cancel terminal UPDATE (conditional on subject_key), independent of sandbox_id.
  • Drop the now-redundant post-update release; sandbox cleanup path unchanged.
  • Add regression tests: subject run without sandbox releases, ordinary runs untouched, cancelled subject routes resubmission to a new run via continue_conversation.

Issue Number

Fixes #472

How to Test

python -m pytest tests/test_cancel_run.py tests/test_conversations.py tests/test_watchdog.py -q — 109 passed. The two new subject tests fail on base and pass with the fix. ruff check / ruff format --check clean.

Type

  • Bug fix
  • Feature
  • Refactor
  • Breaking change
  • Docs / chore

Co-authored-by: openhands <openhands@all-hands.dev>
@github-actions github-actions Bot added the type: fix A bug fix label Sep 18, 2026
@onatozmenn
onatozmenn marked this pull request as ready for review September 18, 2026 13:32
Copilot AI lite review requested due to automatic review settings September 18, 2026 13:32
@all-hands-bot

Copy link
Copy Markdown
Contributor

👋 This PR needs a couple of things fixed before OpenHands can review it:

  • the PR description's HUMAN: section needs at least 20 characters describing what you tested, not just the template placeholder

Push an update once this is addressed and this check re-runs automatically.

This is an automated check - no AI was used to generate this comment.

1 similar comment
@all-hands-bot

Copy link
Copy Markdown
Contributor

👋 This PR needs a couple of things fixed before OpenHands can review it:

  • the PR description's HUMAN: section needs at least 20 characters describing what you tested, not just the template placeholder

Push an update once this is addressed and this check re-runs automatically.

This is an automated check - no AI was used to generate this comment.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

The regression test does not exercise an actual resubmission after cancellation.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

This pull request fixes cancellation so conversation subjects are released atomically, including runs without sandboxes.

Changes:

  • Updates cancellation to set subject_released_at.
  • Removes redundant post-update release logic.
  • Adds cancellation regression tests.
File summaries
File Description
tests/test_cancel_run.py Tests subject release and ordinary cancellations.
openhands/automation/router.py Atomically releases subjects during cancellation.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread tests/test_cancel_run.py Outdated
Co-authored-by: openhands <openhands@all-hands.dev>
@onatozmenn

Copy link
Copy Markdown
Author

Good catch — the resubmission test now goes through the real routing path: it cancels a queued subject run, then calls continue_conversation for the same subject and asserts needs_run is True. On base this fails (the turn gets coalesced into the dead run, so no replacement run is ever created); with the fix it passes.

@all-hands-bot all-hands-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This review was posted by an AI agent (OpenHands).

Summary

The fix is correct and minimal. cancel_run now stamps subject_released_at inside the same optimistic-locking UPDATE that transitions the run to CANCELLED, guarded by a CASE on subject_key IS NOT NULL, and drops the previous post-update release that only ran when the run had a sandbox_id.

Verification performed

  • Fetched and checked out the exact head 06557b68378c2f79edc5637bd7779aefd24771bf (detached HEAD), and re-confirmed the PR head is still that SHA immediately before publishing.
  • Read AGENTS.md and .agents/skills/custom-codereview-guide.md and applied that guidance.
  • Confirmed the new CASE expression compiles to valid SQL for both supported dialects (CASE WHEN automation_runs.subject_key IS NOT NULL THEN :now ELSE automation_runs.subject_released_at END), and executed it against SQLite to confirm the semantics: a run with a subject gets subject_released_at set, a run without one is left untouched, and a previously-released subject is not overwritten (the else_ branch preserves the earlier timestamp).
  • Traced the routing path in conversations.py (_lock_subject_run filters on subject_key == ... AND subject_released_at IS NULL) and ingest.py, confirming that releasing the subject on cancel is what makes a resubmitted event route to a new run, which is the reported behavior in #472.

Correctness notes

  • The previous release path was gated on run.sandbox_id, which is exactly why local-mode and conversation-turn child runs (often without a sandbox) stranded the subject. Moving the release into the terminal UPDATE makes it independent of sandbox_id, and keeping the CASE guard means ordinary runs (no subject_key) still leave both subject columns NULL. This matches the acceptance criteria in #472.
  • Atomicity is preserved: the release rides the existing UPDATE ... WHERE status IN (PENDING, RUNNING) with the rowcount == 0 conflict check, so a concurrent cancel/callback cannot leave a terminated run still holding a subject.
  • Sandbox cleanup is unchanged in effect: the if run.sandbox_id: block still deletes the sandbox for a cancelled run, and its comment now correctly reflects that the subject was already released by the terminal update.

Non-blocking observations (not blocking merge)

  • test_cancelled_subject_no_longer_blocks_resubmission exercises the real routing decision via continue_conversation and asserts needs_run is True, but stops short of asserting that a replacement run row is actually created and owns the subject. That end-to-end assertion would more directly cover the third acceptance criterion in #472. The current coverage is still meaningfully stronger than the earlier version Copilot flagged, and run creation after needs_run is exercised elsewhere.
  • I could not execute the pytest suite in this environment: the test fixtures require a PostgreSQL container via testcontainers and Docker is unavailable here, so I relied on static tracing plus direct dialect/behavior checks of the changed statement. On GitHub, the fork PR's Run tests workflow for this head shows action_required (awaiting approval), so the unit-test results are not yet visible in CI; the author reports 109 passing locally.

No blocking correctness, security, or design issues were found in the changed lines. The change does what the description claims, with no side effects on non-subject runs.

✅ APPROVED

@VascoSch92 VascoSch92 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

thanks

@VascoSch92
VascoSch92 enabled auto-merge (squash) September 24, 2026 13:28
@onatozmenn

Copy link
Copy Markdown
Author

Hi maintainers — the current ci, Run tests, and Docker workflows are awaiting maintainer approval, so the PR's tests have not run yet. Could someone approve these runs so CI can execute? I’ll address any failures once the results are available.

Comment thread openhands/automation/router.py
Guard update_sandbox_id with WHERE status == RUNNING so a run cancelled mid-provisioning never gains a sandbox afterwards. _execute_run releases the just-provisioned context instead when the record is skipped, closing the fork + leak in the cancel-during-provisioning window while keeping the eager subject release.

Co-authored-by: openhands <openhands@all-hands.dev>
auto-merge was automatically disabled October 1, 2026 20:38

Head branch was pushed to by a user without write access

This branch has not been deployed

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

Labels

type: fix A bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Release conversation subjects when runs are cancelled

4 participants