Skip to content

fix: prevent session cancel and clear races - #523

Merged
pedronauck merged 1 commit into
mainfrom
issue-522
Sep 1, 2026
Merged

pedronauck merged 1 commit into
mainfrom
issue-522

Conversation

@pedronauck

@pedronauck pedronauck commented Sep 1, 2026 •

Copy link
Copy Markdown
Member

What & why

Fixes #521.
Fixes #522.

This PR removes two timing-dependent session failures that meet in the same operator flow: cancel an active prompt, clear the conversation, and continue using the durable session.

#522 — a late prompt cancel could cancel the next turn

Driver.Cancel previously had two different cancellation behaviors:

  1. while a prompt was active, it canceled the prompt's local context; and
  2. after that prompt had already settled, it fell back to ACP session/cancel.

That fallback was unsafe because session/cancel belongs to the whole ACP session rather than one request. A delayed or repeated cancel could therefore arrive after turn A settled and cancel turn B. Prompt-context cancellation also started a second notifier that emitted the same session-scoped notification, even though the ACP SDK already maps context cancellation to request-scoped $/cancel_request.

The fix makes prompt cancellation own only the active request:

  • Driver.Cancel cancels the active prompt context and becomes an idempotent no-op after settlement;
  • the redundant session-scoped prompt notifier is removed;
  • the existing ACP SDK request cancellation remains the transport for an active prompt; and
  • whole-session Stop sends exactly one session/cancel directly from cancelSessionForStop.

The owning ACP lifecycle suite now proves all four issue invariants: active cancellation still works, no session-scoped notification is sent for active or late prompt cancellation, Stop sends exactly one session-scoped cancel, and the next turn completes after a settled late cancel.

#521 — transcript reads could enter the clear replacement window

Conversation clear already serialized its writer and quiesced the per-session SQLite family, but it published conversationFinalizing too late: only after stopping the active runtime and inside the database replacement operation. A transcript reader could resolve the stored recorder immediately before quiescence, fail its read-only open with ErrReadOnlyPoolQuiescing, normalize that temporary state to session not found, and enter recovery while clear was replacing the database family.

That is the same sequence recorded in recent ci.yml Web E2E failures: backup completed, a concurrent transcript read reported session not found, then the clear endpoint returned HTTP 500.

The fix makes the clear transition atomic to readers:

  • publish the conversation-finalization barrier immediately after acquiring the serialized conversation-operation lock, before Stop;
  • keep that barrier active through Stop, backup, database replacement, and runtime restart;
  • let only the context that owns that exact conversation operation bypass its own barrier, preventing Clear → Stop self-deadlock;
  • make query recorder resolution and clear-recovery paths wait for finalization; and
  • retry one read-only store open after ErrReadOnlyPoolQuiescing, using the canonical resolved session ID.

The existing clear suite owns the regression. Its deterministic quiescence hook now proves that a transcript read waits for clear instead of returning a transient missing-session error.

CI correlation

The #521 race is directly correlated with the repeated E2E web (shard 3/4) failure at web/e2e/__tests__/session-hardening.spec.ts:314 in recent main runs:

  • run 33538539190 / job 99958871506;
  • run 33535868025 / job 99950062419; and
  • run 33528977779 / job 99927127061.

#522 is adjacent to the same cancel → clear scenario and explains flaky prompt-cancel/next-turn behavior, but it is not the source of the clear endpoint's recorded HTTP 500. Other recent E2E failures involving the Loops builder, OS shell performance, terminal locators, and Desktop teardown have different failure signatures and are deliberately outside this PR.

How you verified it

Focused regressions

  • go test -race ./internal/acp -run 'TestDriverCancelPreservesTurnScope|TestStopCancelsANonInspectionSession' -count=1
    • 5 tests passed.
  • go test -race ./internal/session -run 'TestClearConversationRestartsSameSessionWithFreshContext' -count=1
    • 3 tests passed.
  • Both regressions also passed 10 consecutive race-enabled repetitions during development.
  • Full race-enabled internal/acp and internal/session package suites passed before the final scoped gate.
  • GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build ./internal/acp ./internal/session
    • passed, covering the ACP subprocess changes on Windows.

Required local gate

  • make gate
    • affected scopes: ./internal/acp/... and ./internal/session/...;
    • Go source policies: pass;
    • golangci-lint: 0 issues;
    • race-enabled tests: all pass;
    • current gate fingerprint: d992f081f6a9cdd0e1107f6f9070e02061112897.

Per repository policy, I did not run make gate-full or the full Web E2E matrix locally. The exact-head PR CI run owns those heavy lanes.

Public-surface QA

The targeted isolated lab used a real Codex provider and exercised CLI, HTTP, UDS, native-tool, runtime, and Web surfaces.

  • Active CLI prompt cancellation returned canceled for turn turn-8a40d61cac8706d7.
  • Repeated CLI, HTTP, and compozy__session_prompt_cancel calls returned nothing-in-flight.
  • The same durable session completed NEXT_TURN_OK after the canceled turn.
  • Clear returned HTTP 200, preserved the durable session ID, advanced the transcript epoch, and fresh HTTP plus UDS reads returned zero entries.
  • The restarted provider session completed CLEAR_NEXT_OK after clear.
  • A separate populated Web session completed the Clear conversation confirmation flow, rendered an empty thread, and independently returned zero transcript entries.
  • RT-session-prompt-cancel and RT-017 now record qa_status: pass.
  • The strict QA evidence audit reports zero blockers and zero warnings.
  • Mandatory teardown records "clean": true with no surviving processes.

The adjacent stopped-session canary encountered the already-open BUG-20260825-workspace-agent-unusable-for-sessions. That known workspace-profile agent resolution defect occurs after the in-scope clear and post-clear prompt have completed and is not changed here.

Authorship: Codex implemented and co-wrote this change; I inspected the final diff and verified it through the commands and public-surface walks above.

Impact

  • CLI: no command or output-schema change. compozy session prompt-cancel keeps the existing canceled / nothing-in-flight contract and becomes reliably turn-scoped.
  • HTTP/UDS: no route or schema change. Existing prompt-cancel and clear endpoints keep their public shapes; clear no longer exposes the transient database replacement window to readers.
  • Web: no Web source change. The existing optimistic clear flow and rollback behavior remain intact; the targeted Web walk passed, and session-hardening.spec.ts remains the full E2E backstop in PR CI.
  • Configuration: no config.toml key, default, lifecycle, or Settings surface changes. Searches across runtime config and Settings found no cancellation or clear setting to update or remove.
  • Docs: no packages/site product-doc change is needed because the existing lifecycle and orchestration docs already state the intended public behavior: prompt cancel preserves the session and repeat cancellation is safe. The living QA scenarios and run report are updated with fresh evidence.
  • Storage: no schema or migration change. The existing per-session database family and clear protocol are unchanged on disk; only the synchronization boundary and read retry behavior change.

Compozy Impact Audit

  • Native tools: compozy__session_prompt_cancel keeps the same tool ID, toolset membership, descriptor, input/output schemas, digests, risk flags, availability gates, and CLI/API fallbacks. Its existing production path now inherits the corrected request-scoped runtime behavior. Verified through descriptor inspection and live invocation.
  • Extensibility and hooks: no extension manifest, hook event, skill/capability, resource, registry, bridge SDK, MCP sidecar, or config-lifecycle change. The ACP provider transport is corrected below those stable extension surfaces.
  • Workspace data isolation: changed data remains session-scoped. No new datum, cache, SSE frame, or event type is introduced. Existing workspace/session ownership continues through CLI/HTTP/UDS → session manager → session database family; the new waits key off the already-resolved session owner and cannot cross workspace boundaries.
  • Official Compozy skill: no update required. Checked skills/compozy/references/runtime-operations.md, skills/compozy/references/native-tools.md, and their bundled mirrors; they already document idempotent prompt cancellation, session preservation, and whole-session Stop separately.

Risk and rollback

The main risk is deadlock or over-serialization around conversation replacement. The operation-owner marker limits barrier bypass to the context holding the exact session's conversation lock, while independent readers still obey caller cancellation. Race-enabled package tests, deterministic clear quiescence, real provider QA, and the Web walk cover those boundaries.

There is no migration or compatibility bridge to roll back. Reverting this single commit restores the previous behavior, though it would also restore both races.


  • make gate passes locally; this PR is delivered only after its required CI checks are green
  • New or changed behavior is covered by tests, or I explained above why not
  • If an agent wrote or co-wrote this, I named it above and verified the result myself

Summary by CodeRabbit

  • Bug Fixes
    • Improved prompt cancellation so it only affects the active turn and no longer interrupts subsequent turns.
    • Ensured session cancellation notifications are sent consistently when stopping sessions.
    • Prevented conversation clearing and transcript access from racing with finalization.
    • Improved recovery and retry handling while session data is temporarily unavailable.
  • Tests
    • Added coverage for turn-scoped cancellation, session stop notifications, and transcript access during conversation clearing.

@vercel

vercel Bot commented Sep 1, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
compozy-site Ready Ready Preview Sep 1, 2026 6:54pm UTC

Request Review

@coderabbitai

coderabbitai Bot commented Sep 1, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: d232e574-dece-404a-bf00-516a38f05349

📥 Commits

Reviewing files that changed from the base of the PR and between 320481a and f89f7a1.

⛔ Files ignored due to path filters (3)
  • docs/qa/reports/2026-09-01-issue-521-522-session-cancel-clear.md is excluded by !**/*.md
  • docs/qa/scenarios/RT-017.md is excluded by !**/*.md
  • docs/qa/scenarios/RT-session-prompt-cancel.md is excluded by !**/*.md
📒 Files selected for processing (11)
  • internal/acp/client_control.go
  • internal/acp/client_process_lifecycle_test.go
  • internal/acp/client_prompt.go
  • internal/acp/client_start_contract_test.go
  • internal/session/manager.go
  • internal/session/manager_clear.go
  • internal/session/manager_clear_operation.go
  • internal/session/manager_clear_test.go
  • internal/session/manager_conversation_operation.go
  • internal/session/manager_session_db_family.go
  • internal/session/query_recorder.go
💤 Files with no reviewable changes (1)
  • internal/session/manager_clear_operation.go

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.


Walkthrough

The ACP driver now separates active-prompt cancellation from session cancellation. Session clearing marks conversations as finalizing, avoids self-waiting, and coordinates database recovery and query-recorder access with finalization state.

Changes

Session lifecycle coordination

Layer / File(s) Summary
Prompt-scoped cancellation
internal/acp/client_control.go, internal/acp/client_prompt.go, internal/acp/client_process_lifecycle_test.go, internal/acp/client_start_contract_test.go
Driver.Cancel is an idempotent no-op after a prompt settles and cancels only the active prompt. Session stop sends session/cancel directly. Prompt execution uses SDK request-scoped cancellation. Tests cover late, active, and session-stop cancellation.
Conversation finalization ownership
internal/session/manager_conversation_operation.go, internal/session/manager_clear.go, internal/session/manager_clear_operation.go, internal/session/manager.go, internal/session/manager_clear_test.go
Conversation ownership detection bypasses self-waiting. ClearConversation holds finalization state around the clear operation. Transcript reads are tested during store quiescence.
Recovery and query coordination
internal/session/manager_session_db_family.go, internal/session/query_recorder.go
Session database recovery and recorder target resolution wait for conversation finalization. Recorder opening retries once when the read-only pool is quiescing.

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

Merge Risk: ⚪ Minimal · up to f89f7

The PR narrows prompt cancellation to the active request and prevents conversation reads from entering the clear replacement window; focused race tests, package gates, build checks, and public-surface QA passed. No actionable merge-blocking risk remains beyond normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Driver
  participant AgentProcess
  participant ACPAgent
  Client->>Driver: Cancel prompt
  Driver->>AgentProcess: cancelCurrentPrompt()
  AgentProcess->>ACPAgent: $/cancel_request for active prompt
  Client->>Driver: Stop session
  Driver->>ACPAgent: session/cancel
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 8 functions across 10 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes both primary changes: preventing session-cancel races and conversation-clear races.
Linked Issues check ✅ Passed The changes satisfy #522 by making Driver.Cancel turn-scoped and idempotent after prompt settlement, moving session cancellation to the Stop path, and adding regression coverage. The changes satisfy #…
Out of Scope Changes check ✅ Passed All reviewed changes support the linked objectives. The implementation changes, synchronization logic, and regression tests are directly related to session cancellation and conversation-clear race pre…
Full details: Linked Issues check

Explanation

The changes satisfy #522 by making Driver.Cancel turn-scoped and idempotent after prompt settlement, moving session cancellation to the Stop path, and adding regression coverage. The changes satisfy #521 by keeping conversation finalization active through clearing, making readers and recovery paths wait, retrying quiescing recorder opens, and adding clear/read synchronization coverage. Excluded QA files contain non-coding follow-up work and do not prevent validation.

Full details: Out of Scope Changes check

Explanation

All reviewed changes support the linked objectives. The implementation changes, synchronization logic, and regression tests are directly related to session cancellation and conversation-clear race prevention.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-522

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.

@pedronauck
pedronauck marked this pull request as ready for review September 1, 2026 19:40
@greptile-apps

greptile-apps Bot commented Sep 1, 2026

Copy link
Copy Markdown

Greptile Summary

This PR makes prompt cancellation request-scoped and extends conversation-clear finalization across Stop, database replacement, and restart so transcript readers cannot enter the replacement window.

  • Makes late prompt cancellation an idempotent no-op while preserving whole-session cancellation for Stop.
  • Moves the conversation-finalization barrier to cover the complete clear lifecycle and adds an operation-owner bypass to avoid self-deadlock.
  • Makes recorder resolution wait for clear finalization and retries a read-only open after pool quiescence.
  • Adds race-oriented lifecycle and transcript-read regression coverage and updates the associated QA evidence.

Confidence Score: 5/5

The PR appears safe to merge, with no concrete changed-code defect identified in the cancellation or conversation-clear synchronization paths.

Prompt cancellation remains tied to the active ACP request, whole-session Stop retains one session cancellation, and the clear operation consistently carries its ownership marker while independent transcript readers wait until replacement and restart complete.

Important Files Changed

Filename Overview
internal/acp/client_control.go Restricts Driver.Cancel to the active prompt and sends the sole session-scoped cancellation directly from whole-session Stop.
internal/acp/client_prompt.go Removes the redundant session-scoped cancellation notifier and relies on the request context for ACP request cancellation.
internal/session/manager_clear.go Extends conversation finalization across the full serialized clear operation, including Stop and restart.
internal/session/manager_conversation_operation.go Adds target-specific operation ownership to let a clear operation bypass its own finalization barrier.
internal/session/query_recorder.go Waits for conversation finalization before recorder resolution and retries once after read-only pool quiescence.
internal/session/manager_session_db_family.go Prevents query-driven clear recovery from running while conversation finalization is active.
internal/session/manager_clear_test.go Adds deterministic coverage proving concurrent transcript reads wait during clear quiescence.

Sequence Diagram

sequenceDiagram
  participant Client
  participant Manager
  participant Session
  participant Store
  participant Reader

  Client->>Manager: ClearConversation(sessionID)
  Manager->>Manager: Lock conversation operation
  Manager->>Manager: Publish finalization barrier
  Manager->>Session: Stop using owner context
  Session-->>Manager: Stopped
  Manager->>Store: Quiesce, backup, and replace DB family
  Reader->>Manager: TranscriptPage(sessionID)
  Manager-->>Reader: Wait on finalization barrier
  Manager->>Session: Restart durable session
  Manager->>Manager: End finalization barrier
  Manager-->>Reader: Resolve canonical recorder
  Reader->>Store: Open read-only recorder
  Store-->>Reader: Fresh empty transcript
  Manager-->>Client: Cleared session
Loading

Reviews (1): Last reviewed commit: "fix: prevent session cancel and clear ra..." | Re-trigger Greptile

@pedronauck
pedronauck merged commit dfcea62 into main Sep 1, 2026
70 of 74 checks passed
@pedronauck
pedronauck deleted the issue-522 branch September 1, 2026 20:00
This was referenced Sep 27, 2026

This branch was successfully deployed

1 active deployment
Preview — f89f7a1b Deployed Sep 1, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant