fix: release conversation subject in cancel terminal update - #490
onatozmenn wants to merge 8 commits into
Conversation
Co-authored-by: openhands <openhands@all-hands.dev>
|
👋 This PR needs a couple of things fixed before OpenHands can review it:
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
|
👋 This PR needs a couple of things fixed before OpenHands can review it:
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. |
There was a problem hiding this comment.
🟡 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.
Co-authored-by: openhands <openhands@all-hands.dev>
|
Good catch — the resubmission test now goes through the real routing path: it cancels a queued subject run, then calls |
all-hands-bot
left a comment
There was a problem hiding this comment.
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.mdand.agents/skills/custom-codereview-guide.mdand applied that guidance. - Confirmed the new
CASEexpression 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 getssubject_released_atset, a run without one is left untouched, and a previously-released subject is not overwritten (theelse_branch preserves the earlier timestamp). - Traced the routing path in
conversations.py(_lock_subject_runfilters onsubject_key == ... AND subject_released_at IS NULL) andingest.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 terminalUPDATEmakes it independent ofsandbox_id, and keeping theCASEguard means ordinary runs (nosubject_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 therowcount == 0conflict 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_resubmissionexercises the real routing decision viacontinue_conversationand assertsneeds_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 afterneeds_runis exercised elsewhere.- I could not execute the pytest suite in this environment: the test fixtures require a PostgreSQL container via
testcontainersand Docker is unavailable here, so I relied on static tracing plus direct dialect/behavior checks of the changed statement. On GitHub, the fork PR'sRun testsworkflow for this head showsaction_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
|
Hi maintainers — the current |
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>
Head branch was pushed to by a user without write access
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 nosandbox_id, sosubject_released_atstayed unset and a resubmitted subject kept deduplicating against the cancelled run instead of creating a replacement (#472).Summary
subject_released_atatomically in the cancel terminalUPDATE(conditional onsubject_key), independent ofsandbox_id.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 --checkclean.Type