Skip to content

feat: extract runtime turn error handler - #458

Merged
delete-cloud merged 1 commit into
mainfrom
dogfood/runtime-turn-error-actions
Jun 2, 2026
Merged

delete-cloud merged 1 commit into
mainfrom
dogfood/runtime-turn-error-actions

Conversation

@delete-cloud

@delete-cloud delete-cloud commented Jun 2, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • add RuntimeTurnErrorHandler to own local daemon fatal/cancelled/generic turn error actions
  • keep wire error notification in SessionManager as an injected callback so runs does not depend on wire protocol types
  • replace inline run_agent error action bodies with lifecycle helper calls
  • add focused lifecycle tests and update ADR-0058 follow-up status

Verification

  • uv run pytest tests/coding_agent/test_run_lifecycle.py -v -> 14 passed
  • uv run pytest tests/ui/test_session_manager_runtime.py -k "run_agent_marks_agent_run_failed_when_turn_outcome_errors or run_agent_records_error_outcome_without_runtime_store or run_agent_reraises_fatal_tool_execution_error_without_sending_error_turn or run_agent_persists_agent_run_lifecycle_when_store_configured" -v -> 4 passed
  • uv run ruff check src/coding_agent/runs src/coding_agent/server/session_manager.py tests/coding_agent/test_run_lifecycle.py tests/ui/test_session_manager_runtime.py -> passed
  • uv run ruff format --check src/coding_agent/runs/lifecycle.py src/coding_agent/runs/__init__.py src/coding_agent/server/session_manager.py tests/coding_agent/test_run_lifecycle.py -> passed
  • git diff --check -> clean

Review policy

If CodeRabbit is rate-limited, this PR requires local subagent P1/P2 review before merge. Do not merge on a rate-limited CodeRabbit placeholder alone.

Summary by CodeRabbit

  • Refactor

    • Enhanced error handling architecture to improve system reliability and maintainability.
  • Tests

    • Added comprehensive test coverage for error handling scenarios, including fatal errors, cancellation, and generic failures.

@coderabbitai

coderabbitai Bot commented Jun 2, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR refactors local daemon turn error handling by extracting fatal, cancelled, and generic error flows from SessionManager.run_agent into a dedicated RuntimeTurnErrorHandler helper class. New protocols formalize session closure and error notification contracts, the handler is integrated into SessionManager's exception routing, and comprehensive tests validate all error paths.

Changes

Local Daemon Turn Error Handler

Layer / File(s) Summary
Error Handler Contracts
src/coding_agent/runs/lifecycle.py
RuntimeTurnErrorSession protocol extends RuntimeTurnSession and RuntimeRunSession; RuntimeSessionCloser and RuntimeGenericErrorNotifier protocols define session closure and generic error notification interfaces.
RuntimeTurnErrorHandler Implementation
src/coding_agent/runs/lifecycle.py
Dataclass with handle_fatal, handle_cancelled, handle_generic async methods that record observations, finish turn runs with appropriate status/error details, update session state, close runtime for fatal/generic paths, and trigger generic error notifications.
Public API Export
src/coding_agent/runs/lifecycle.py, src/coding_agent/runs/__init__.py
RuntimeTurnErrorHandler and RuntimeTurnErrorSession are added to __all__ exports and re-exported through the runs module for public use.
SessionManager Error Routing Refactor
src/coding_agent/server/session_manager.py
Imports RuntimeTurnErrorHandler, creates observation callback helpers, instantiates the handler, rewrites _on_local_daemon_turn_error to classify exceptions and delegate to handler methods, and updates outer exception handling to route FatalToolExecutionError, CancelledError, RunCoordinatorError, and generic exceptions through the handler.
Runtime Error Handler Test Coverage
tests/coding_agent/test_run_lifecycle.py
Three async tests validate handle_generic records failure status and details, invokes observation and notification callbacks, and closes runtime; handle_cancelled updates to cancelled status without closing runtime; and handle_generic with ensure_started=True can start a queued run before recording failure.
Task Prompt and ADR Status Update
.opencode/prompts/tasks/local-daemon-pr35-turn-error-handler.md, docs/adr/0058-local-daemon-control-plane-executor-architecture.md
Task prompt documents the refactor scope and testing steps; ADR 0058 follow-up status is updated to mark delegation of local daemon turn error actions to RuntimeTurnErrorHandler as completed.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • delete-cloud/Coding-Agent-Loop#384: Both PRs modify SessionManager.run_agent to set session.turn_status and session.last_failure_details based on error/failure handling (main PR via new RuntimeTurnErrorHandler, related PR via TurnOutcome computation).
  • delete-cloud/Coding-Agent-Loop#436: Both PRs refactor local-daemon runtime turn error handling by updating SessionManager.run_agent error-routing logic (main PR via RuntimeTurnErrorHandler delegation, related PR via on_turn_error hook).
  • delete-cloud/Coding-Agent-Loop#106: Both PRs handle FatalToolExecutionError and asyncio.CancelledError propagation (related PR raises exceptions in tool/subagent execution, main PR routes those exceptions through RuntimeTurnErrorHandler in SessionManager.run_agent).

Poem

🐰 Turn errors now dance in a handler so neat,
Protocols guide each fatal retreat,
Cancellations and failures get their own way,
SessionManager's simpler—hurray, hooray!
No more tangled catching in one giant try,
Our error-handler helper lets clarity fly! 🎉

🚥 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%. 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 'feat: extract runtime turn error handler' accurately summarizes the main change: extracting error handling logic into a new RuntimeTurnErrorHandler component from SessionManager.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dogfood/runtime-turn-error-actions

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 and usage tips.

@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 `@coding-agent/src/coding_agent/runs/lifecycle.py`:
- Around line 352-360: The handler handle_cancelled should set the session state
to "cancelled" before finalizing the run so the persisted session reflects the
cancellation; modify RuntimeTurnErrorSession handling in handle_cancelled to
assign session.turn_status = "cancelled" (after invoking
self.cancel_observation() if present) and then call
self.turn_run.finish_if_started(session, status="cancelled", result={},
error="cancelled") so the persisted state is correct.
🪄 Autofix (Beta)

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4c0b7e4f-9671-40b1-bd9c-e77501563ed8

📥 Commits

Reviewing files that changed from the base of the PR and between e905abc and 22fbf11.

📒 Files selected for processing (6)
  • coding-agent/.opencode/prompts/tasks/local-daemon-pr35-turn-error-handler.md
  • coding-agent/docs/adr/0058-local-daemon-control-plane-executor-architecture.md
  • coding-agent/src/coding_agent/runs/__init__.py
  • coding-agent/src/coding_agent/runs/lifecycle.py
  • coding-agent/src/coding_agent/server/session_manager.py
  • coding-agent/tests/coding_agent/test_run_lifecycle.py

Comment on lines +352 to +360
async def handle_cancelled(self, session: RuntimeTurnErrorSession) -> None:
if self.cancel_observation is not None:
self.cancel_observation()
await self.turn_run.finish_if_started(
session,
status="cancelled",
result={},
error="cancelled",
)

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Set the session state to cancelled here.

handle_cancelled() updates the run record, but it never updates session.turn_status. After this refactor, SessionManager.run_agent() persists whatever state the handler leaves behind, so user-initiated cancels now stick at "cancelling" (and some other cancellation paths can fall back to "idle" in the finally block) instead of ending as "cancelled".

Proposed fix
     async def handle_cancelled(self, session: RuntimeTurnErrorSession) -> None:
         if self.cancel_observation is not None:
             self.cancel_observation()
         await self.turn_run.finish_if_started(
             session,
             status="cancelled",
             result={},
             error="cancelled",
         )
+        session.turn_status = "cancelled"
+        session.last_failure_details = None
🤖 Prompt for 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.

In `@coding-agent/src/coding_agent/runs/lifecycle.py` around lines 352 - 360, The
handler handle_cancelled should set the session state to "cancelled" before
finalizing the run so the persisted session reflects the cancellation; modify
RuntimeTurnErrorSession handling in handle_cancelled to assign
session.turn_status = "cancelled" (after invoking self.cancel_observation() if
present) and then call self.turn_run.finish_if_started(session,
status="cancelled", result={}, error="cancelled") so the persisted state is
correct.

@delete-cloud

Copy link
Copy Markdown
Owner Author

Local review fallback because CodeRabbit remained pending without producing an actionable review.

Reviewer: Epicurus (local subagent)
Scope: P1/P2 blockers only for #458 diff vs origin/main.

Findings:

  • No P1/P2 findings.

Review notes:

  • Fatal errors still fail observation, finish run as failed, set turn status/failure details, close runtime, and re-raise.
  • Cancelled errors still cancel observation, finish run as cancelled, do not close runtime, and re-raise.
  • Generic errors still fail observation, finish run as failed, set failure details, close runtime, and emit wire error messages.
  • RunCoordinatorError still uses ensure_started=True, preserving create/start-then-fail semantics.
  • runs/lifecycle.py does not depend on SessionManager or wire protocol classes; wire notification remains injected from SessionManager.

Additional verification from reviewer:

  • uv run pytest tests/coding_agent/test_run_lifecycle.py -q -> 14 passed
  • uv run ruff check src/coding_agent/runs src/coding_agent/server/session_manager.py tests/coding_agent/test_run_lifecycle.py tests/ui/test_session_manager_runtime.py -> passed

Residual risk:

  • Focused verification only, not full-suite.
  • Cancel no-close integration is covered at helper level rather than a dedicated SessionManager adapter-cancel test.
  • Fatal handler has integration coverage through the existing fatal UI regression rather than a dedicated helper unit.

Merge decision under fallback policy: OK to merge.

@delete-cloud
delete-cloud merged commit 801047f into main Jun 2, 2026
1 check passed
@delete-cloud
delete-cloud deleted the dogfood/runtime-turn-error-actions branch June 2, 2026 22:22
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.

1 participant