Skip to content

fix: recover canceled Loop coordinators on restart - #353

Merged
pedronauck merged 2 commits into
mainfrom
issue-348
Aug 12, 2026
Merged

pedronauck merged 2 commits into
mainfrom
issue-348

Conversation

@pedronauck

@pedronauck pedronauck commented Aug 12, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Restarts no longer fail when a nonterminal Loop owns a canceled coordinator task or when cancellation reaches an already-removed managed session.
  • Recovery reopens only the Loop-owned coordinator task, creates one causal replacement run, and remains idempotent across repeated boot and scheduler reconciliation.
  • Public Loop guidance and the official Compozy skill now describe the missing-session and restart-recovery boundaries.

Root cause

Cancellation could leave three durable facts out of agreement: the Loop remained nonterminal, its reusable coordinator task was canceled, and one or more managed sessions had already disappeared. Boot reconciliation correctly detected that the Loop still needed a coordinator, but reservation reused the closed task and failed with task: invalid status transition before daemon readiness. The daemon adapter also treated session.ErrSessionNotFound as failed delivery, so the cancellation backstop kept revisiting a stop that had already happened.

Changes

  • Task projection: canceled coordinator pulses now keep the Loop umbrella task in_progress, matching completed coordinator pulses; canceled worker runs still cancel their tasks.
  • Coordinator recovery: the shared reservation boundary atomically reopens a canceled coordinator task only for a nonterminal Loop, clears closed_at, records the normal task.status_changed event, and reserves one replacement run.
  • Idempotency: recovery identities include the previous coordinator run, so replay reserves nothing while canceling that recovered pulse permits one causally new recovery.
  • Cancellation delivery: a named daemon adapter treats only session.ErrSessionNotFound as already stopped and propagates every other cancel or kill error.
  • Documentation: the Loop operations guide and skills/compozy/references/loops.md document absent-session delivery and restart repair.
  • QA: adds a targeted restart charter, scenario tracker, and dated report covering repeated cancellation, four daemon starts, CLI/HTTP agreement, and a foreign-workspace isolation canary.

Release Notes

🐛 Bug Fixes

  • Recover canceled Loop coordinator tasks during daemon restart and treat already-absent managed sessions as stopped, preventing one damaged Loop from blocking daemon readiness.

QA

Final Status: PASS — missing-session cancellation and repeated restart recovery passed; teardown is clean and the full automated suite is green.

Coverage: Bruno completed one Interrupt Tour across J-recover-loop-node-failure, including affected and foreign workspace reads through CLI, HTTP, UDS, runtime, scheduler, task, session, Loop, and deterministic ACP subprocess surfaces.

Issues: 0 new issues (Blocks-Completion: 0, Data-Loss: 0, Trust-Damage: 0, Friction: 0, Cosmetic: 0).

Blocked / needs human: None.

Full report: 2026-08-12-issue-348-loop-cancel-restart.md

Compozy Impact Audit

  • Native tools: no tool IDs, toolsets, descriptors, schemas, digests, risk flags, availability diagnostics, or capability gates changed. Existing Loop cancel and kill tools use the corrected backend behavior.
  • Extensibility and hooks: no extension, bridge SDK, MCP sidecar, registry, resource, or config lifecycle changed. Repair uses the existing task.status_changed hook path, and Loop terminal delivery remains unchanged.
  • Workspace data isolation: Loop, task, run, event, and cancellation data remain workspace-scoped. Recovery preserves the stored workspace_id; canonical store tests and the public foreign-workspace QA read prove no cross-workspace result is exposed through CLI, HTTP/UDS, or SSE-backed state.
  • Official Compozy skill: skills/compozy/references/loops.md now tells agents that an absent managed session is already stopped and that only a nonterminal Loop can recover its canceled coordinator task.

Web / Docs / Config impact

  • No Web route, component, hook, DTO, OpenAPI contract, generated TypeScript contract, or settings surface changes.
  • No config.toml key, default, apply lifecycle, schema migration, or storage migration changes.
  • The public Loop guide changes because restart recovery is operator-visible. Historical release artifacts remain unchanged.

Review remediation

  • CodeRabbit round 1: one minor testing finding. Commit 2873ffa now asserts the exact stop cause and detail forwarded by the daemon cancellation adapter; the focused race test and the full iteration gate passed.
  • CodeRabbit round 2: approved on 2873ffa with no new findings; the round 1 thread is resolved.

Test plan

  • Task policy regression: CGO_ENABLED=1 go test -race ./internal/task -run '^TestTaskStatusFromPolicySnapshotPrecedence$'
  • Store regressions: race-enabled canonical Loop cancellation/recovery and task-catalog suites.
  • Daemon adapter regression: CGO_ENABLED=1 go test -race ./internal/daemon -run '^TestLoopCancellationSessionControllerShouldTreatMissingSessionsAsStopped$'
  • Goal-managed runtime integration: race-enabled TestLoopGoalManagedRuntimeIntegration cancellation paths.
  • Historical migration timeout canary passed alone in 22 seconds.
  • Iteration gates escalated to the full make verify: 21,511 tests passed, 2 expected skips, Web/Bun gates, Go lint, Go build, codegen checks, and package boundaries passed. The authoritative final content fingerprint is recorded by make gate-status after the closing gate.
  • Isolated public-interface QA passed; strict evidence audit has no blockers or warnings; teardown.json reports clean: true with no survivors.

Closes #348

@vercel

vercel Bot commented Aug 12, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
compozy-site Ready Ready Preview Aug 12, 2026 5:13am

Request Review

@coderabbitai

coderabbitai Bot commented Aug 12, 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: Pro Plus

Run ID: 7bc6e2a1-72f3-4ccf-b162-414e359f4727

📥 Commits

Reviewing files that changed from the base of the PR and between f223571 and 2873ffa.

📒 Files selected for processing (1)
  • internal/daemon/loop_runtime_adapters_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/daemon/loop_runtime_adapters_test.go

Walkthrough

The change centralizes loop session cancellation, preserves umbrella task progress for canceled coordinator runs, and repairs canceled coordinator tasks during loop reconciliation. Recovery uses the previous coordinator run ID and a dedicated idempotency key.

Changes

Loop cancellation recovery

Layer / File(s) Summary
Coordinator task status policy
internal/store/globaldb/global_db_task_catalog_sql.go, internal/task/manager_reconcile_status.go, internal/store/globaldb/global_db_task_test.go, internal/task/manager_test.go
Canceled coordinator runs keep umbrella tasks in_progress. Canceled worker runs remain canceled.
Coordinator task recovery
internal/store/globaldb/global_db_loop_coordinator_seed.go, internal/store/globaldb/global_db_loop_reconcile_queries.go, internal/store/globaldb/queries/loop_runtime.sql, internal/store/globaldb/sqlcgen/loop_runtime.sql.go, internal/store/globaldb/global_db_loop_test.go
Reconciliation retrieves the previous coordinator run, reactivates canceled coordinator tasks, and reserves replacement runs with recovery-specific idempotency keys.
Session cancellation controller
internal/daemon/loop_runtime_adapters.go, internal/daemon/loop_runtime_adapters_test.go, internal/daemon/loop_api_service.go, internal/daemon/loop_goal_managed_runtime_integration_test.go
Loop cancellation uses a session-manager adapter. Missing sessions are treated as stopped, while other errors propagate.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Daemon
  participant SessionController
  participant SessionManager
  participant LoopReconciliation
  participant GlobalDB
  Daemon->>SessionController: cancel or stop loop session
  SessionController->>SessionManager: CancelPrompt or StopWithCause
  SessionManager-->>SessionController: success, missing session, or error
  Daemon->>LoopReconciliation: reconcile canceled coordinator
  LoopReconciliation->>GlobalDB: retrieve previous coordinator run
  LoopReconciliation->>GlobalDB: repair task and reserve replacement run
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes address #348 by making canceled coordinator recovery restart-safe, idempotent, and tolerant of missing sessions.
Out of Scope Changes check ✅ Passed The code changes remain within the linked issue scope and support Loop cancellation recovery, reconciliation, and regression coverage.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: recovering canceled Loop coordinators after daemon restart.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-348

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.

@greptile-apps

greptile-apps Bot commented Aug 12, 2026 •

Copy link
Copy Markdown

Greptile Summary

The PR repairs canceled coordinator tasks belonging to nonterminal Loops during daemon reconciliation and treats already-absent managed sessions as successfully stopped.

  • Reopens the reusable coordinator task and reserves one causally keyed replacement run.
  • Keeps canceled coordinator pulses from terminalizing their Loop umbrella tasks while preserving worker cancellation.
  • Adds daemon adapter, persistence, policy, integration, QA, public documentation, and official skill coverage.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
internal/store/globaldb/global_db_loop_reconcile_queries.go Recovery now derives a causal idempotency key from the previous coordinator run and performs repair plus reservation through the existing transactional boundary.
internal/store/globaldb/global_db_loop_coordinator_seed.go Adds guarded reopening of canceled coordinator tasks only for nonterminal Loops and clears the task closure timestamp.
internal/task/manager_reconcile_status.go Aligns task projection so canceled Loop coordinator pulses keep their reusable umbrella task in progress while canceled worker runs remain canceled.
internal/store/globaldb/global_db_task_catalog_sql.go Mirrors the task-manager projection in catalog SQL for canceled coordinator pulses.
internal/daemon/loop_runtime_adapters.go Adds a named cancellation adapter that treats only missing-session errors as an already-completed stop and propagates other failures.
internal/store/globaldb/global_db_loop_test.go Covers repair, event creation, repeated boot idempotency, and a causally new recovery after canceling the first replacement.
packages/site/content/docs/loops/running.mdx Documents missing-session cancellation delivery and nonterminal coordinator recovery behavior.
skills/compozy/references/loops.md Updates the official skill reference with the corrected cancellation and restart-recovery boundaries.

Sequence Diagram

sequenceDiagram
  participant Boot as Daemon boot
  participant Store as Global DB
  participant Task as Coordinator task
  participant Run as Coordinator run
  Boot->>Store: Reconcile nonterminal Loops
  Store->>Store: Find Loop missing an active coordinator
  Store->>Task: Reopen canceled task
  Store->>Run: Reserve replacement using prior run identity
  Store-->>Boot: Return one queued recovery run
  Boot->>Store: Repeat reconciliation
  Store-->>Boot: No duplicate reservation
Loading

Reviews (2): Last reviewed commit: "test: assert Loop cancellation stop meta..." | Re-trigger Greptile

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/daemon/loop_runtime_adapters_test.go`:
- Around line 74-78: Update the kill-session assertions in the test’s test.kill
branch to also capture and verify the stop cause and detail passed to
KillLoopSession. Assert session.CauseUserRequested and the exact detail
"operator request", while preserving the existing session ID assertion.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: f9d99dba-6816-4ec0-887f-29da317f74ef

📥 Commits

Reviewing files that changed from the base of the PR and between 452c3d1 and f223571.

⛔ Files ignored due to path filters (5)
  • docs/qa/charters/CH-loop-cancel-restart.md is excluded by !**/*.md
  • docs/qa/reports/2026-08-12-issue-348-loop-cancel-restart.md is excluded by !**/*.md
  • docs/qa/scenarios/LP-cancel-restart-recovers.md is excluded by !**/*.md
  • packages/site/content/docs/loops/running.mdx is excluded by !**/*.mdx
  • skills/compozy/references/loops.md is excluded by !**/*.md
📒 Files selected for processing (13)
  • internal/daemon/loop_api_service.go
  • internal/daemon/loop_goal_managed_runtime_integration_test.go
  • internal/daemon/loop_runtime_adapters.go
  • internal/daemon/loop_runtime_adapters_test.go
  • internal/store/globaldb/global_db_loop_coordinator_seed.go
  • internal/store/globaldb/global_db_loop_reconcile_queries.go
  • internal/store/globaldb/global_db_loop_test.go
  • internal/store/globaldb/global_db_task_catalog_sql.go
  • internal/store/globaldb/global_db_task_test.go
  • internal/store/globaldb/queries/loop_runtime.sql
  • internal/store/globaldb/sqlcgen/loop_runtime.sql.go
  • internal/task/manager_reconcile_status.go
  • internal/task/manager_test.go

Comment thread internal/daemon/loop_runtime_adapters_test.go
@pedronauck

Copy link
Copy Markdown
Member Author

CI baseline evidence before merge: every scoped check for this PR is green, and the final local make gate-full record remains current at fingerprint 5746e5afb9d77a24c3117bdd7cc530c5ca895ad0. The combined E2E lane failed twice on the PR with exactly the same two assertions already failing on its base SHA 452c3d1 (main run 31564670715, job 94020936703): E2E-023 measured an 86 ms frame on main versus 87 ms on the PR (limit 50 ms), and session-provider-override timed out waiting for the same selector scroll predicate. This PR changes no web files or those surfaces. The failures are pre-existing on main and are not caused by #353; no test was weakened or unrelated production code changed.

This branch was successfully deployed

1 active deployment
Preview — 2873ffa0 Deployed Aug 12, 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

Development

Successfully merging this pull request may close these issues.

Daemon cannot start after canceled Loop coordinator reaches invalid transition

1 participant