Skip to content

fix(opencode): transition subagent ToolPart to error state on RunErrorEvent - #24

Merged
Leoyzen merged 2 commits into
develop/agenticfrom
feature/fix-subagent-interupt
Apr 23, 2026
Merged

fix(opencode): transition subagent ToolPart to error state on RunErrorEvent#24
Leoyzen merged 2 commits into
develop/agenticfrom
feature/fix-subagent-interupt

Conversation

@Leoyzen

@Leoyzen Leoyzen commented Apr 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Bug: When a subagent fails (emits RunErrorEvent), the parent session's ToolPart stays in ToolStateRunning forever, making the UI show the subagent as perpetually "running" even after it crashed
  • Root cause: EventProcessor.process() had no match arm for RunErrorEvent — it was silently dropped. The _process_subagent_event() method only handled StreamCompleteEvent (happy path)
  • Fix: Added RunErrorEvent handler in _process_subagent_event() that transitions ToolPart to ToolStateError, emits SessionErrorEvent for the child session, and prevents subsequent StreamCompleteEvent from overriding the error state

Changes

File Change
event_processor.py Add RunErrorEvent + SessionErrorEvent imports; add step 8 handler for RunErrorEvent in _process_subagent_event(); guard StreamCompleteEvent with is_errored check
event_processor_context.py Add is_errored: bool field to prevent error state override
test_subagent_error_state.py New TDD test file with 3 tests

Test plan

  • test_subagent_run_error_transitions_toolpart_to_error — RunErrorEvent transitions ToolPart to ToolStateError with correct error message, emits SessionErrorEvent
  • test_subagent_run_error_without_prior_spawn — RunErrorEvent as first event still creates context and transitions to error
  • test_subagent_stream_complete_after_run_error_stays_error — Error state is terminal, late StreamCompleteEvent doesn't override
  • All 510 existing opencode_server tests pass (0 regressions)

…rEvent

When a subagent fails, it emits RunErrorEvent which was silently dropped
by EventProcessor.process() - no match arm existed for it. The parent
session's ToolPart stayed in ToolStateRunning forever, making the UI show
the subagent as perpetually running even after it crashed.

Changes:
- Add RunErrorEvent handler in _process_subagent_event() that transitions
  ToolPart from ToolStateRunning to ToolStateError with error message
- Emit SessionErrorEvent for child session (matching TS client behavior)
- Add is_errored flag to EventProcessorContext to prevent subsequent
  StreamCompleteEvent from overriding the error state
- Add TDD tests: error transition, error without prior spawn, error
  state terminal (not overridden by late StreamCompleteEvent)

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request implements error handling for subagents by processing RunErrorEvent, ensuring that failures transition the parent's tool part to an error state and are not overwritten by subsequent completion events. It introduces an is_errored flag to the EventProcessorContext and includes comprehensive tests for these scenarios. The review feedback suggests persisting the child session's assistant message to storage upon failure to maintain history consistency and refining the error state logic to better handle out-of-order events and preserve input metadata.

Comment thread src/agentpool_server/opencode_server/event_processor.py
Comment on lines +855 to +869
start_time = (
existing.state.time.start
if isinstance(existing.state, ToolStateRunning)
else now_ms()
)
error_state = ToolStateError(
error=error_msg,
input={
"description": tool_title,
"subagent_type": tool_title,
"prompt": "",
},
metadata={"sessionId": child_session_id, "title": tool_title},
time=TimeStartEnd(start=start_time, end=now_ms()),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The start_time calculation can be improved to handle cases where the subagent might already be in a completed or error state (e.g., due to out-of-order events), as those states also contain the original start time. Additionally, it is safer to reuse the existing input from the tool part to ensure that metadata like the original prompt or description is preserved in the error state.

                    start_time = (
                        existing.state.time.start
                        if hasattr(existing.state, "time") and existing.state.time
                        else now_ms()
                    )
                    error_state = ToolStateError(
                        error=error_msg,
                        input=existing.state.input,
                        metadata={"sessionId": child_session_id, "title": tool_title},
                        time=TimeStartEnd(start=start_time, end=now_ms()),
                    )

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
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