Skip to content

fix(tui_rpc): surface the cause of an internal_error, not just the code - #296

Open
arelchan wants to merge 1 commit into
mainfrom
fix/tui_surface_internal_error_detail
Open

fix(tui_rpc): surface the cause of an internal_error, not just the code#296
arelchan wants to merge 1 commit into
mainfrom
fix/tui_surface_internal_error_detail

Conversation

@arelchan

Copy link
Copy Markdown
Contributor

Summary

A -32603 reached the user as exactly this, and nothing else:

error: [rpc -32603] internal_error

No cause, no file, no log path. The information existed the whole time and was discarded
three separate times:

  1. The dispatcher dropped detail. raven/tui_rpc/dispatcher.py used
    if exc.data is not None: ... elif exc.detail: ..., so a raiser that set both lost the
    detail. _build_tui_agent_loop always sets both.
  2. turn.send emitted only {code, message} for a latched init crash, although the
    ErrorEvent schema already has a detail field and chatStream.onError already renders
    it.
  3. The client formatted [rpc <code>] <message>, where message is a fixed code name
    (internal_error), and every call site prints err.message.

So an AgentLoop that cannot start produced a dead end. A real instance: a config file
containing a key the running branch does not know about fails validation, _build_tui_agent_loop
raises InternalError with the full pydantic message and log_path, and the TUI showed the
line above.

The fix keeps message protocol-faithful and puts the cause where callers already look:

  • error_data(exc) in raven/tui_rpc/errors.py folds detail into data, and both the
    dispatcher and the turn error events use it, so the two paths carry the same context.
  • turn.send passes the init-crash detail plus its log_path into the emitted event.
  • formatRpcError in ui-tui/src/rpc/errors.ts builds the message from detail,
    exception_message or reason, and appends the log path. A multi-line cause (a config
    error listing each offending field) keeps its line breaks below the summary line.

Same failure after the change, rendered by feeding a real dispatcher frame through the client
path:

error: [rpc -32603] internal_error:
Config at /Users/admin/.raven/config.json fails schema validation:
1 validation error for Config
subagents
  Extra inputs are not permitted
(details in ~/.raven/logs/tui.log)

Type

  • Fix
  • Feature
  • Docs
  • CI / tooling
  • Refactor
  • Other

Verification

uv run pytest tests/ -k "tui_rpc or dispatcher or tui_bootstrap" -> 523 passed. New cases:
the dispatcher keeps detail next to reason and log_path in one frame; turn.send emits
the build-error cause with its log path; and it omits detail entirely when the error has no
cause to report.

npx vitest run in ui-tui/ -> 86 files, 988 tests passed, including six new
rpcErrorFromFrame cases: a bare frame keeps the old one-line form; data context is
surfaced with the log path; exception_message and reason are read when detail is absent;
a multi-line cause keeps its shape; non-object or blank data cannot corrupt the message; and
typed subclass selection plus raw data access are unchanged.

npm run type-check, npx eslint, npx prettier --check, ruff check, ruff format --check
all clean.

End to end, the exact production shape: an InternalError raised the way
_build_tui_agent_loop raises it was dispatched to a JSON-RPC frame, and that frame was fed
through rpcErrorFromFrame with tsx, producing the output shown above.

  • Relevant tests pass locally
  • Relevant lint / type checks pass locally
  • User-facing docs or screenshots are updated when needed

Risk

  • Security impact considered
  • Backward compatibility considered
  • Rollback path is clear for risky changes

Notes: error.code and error.message are unchanged on the wire, so nothing that branches on
them is affected; the added data.detail and payload.detail are both already in the schema.
err.message gains a suffix, which the one message-matching call site
(SESSION_BUSY_RE in useSubmission.ts) is unaffected by, since it matches text that still
appears. Detail text originates from server-side exceptions and is already written to
~/.raven/logs/tui.log, so this exposes nothing new to the client; the client accepts it only
when it is a string and ignores any other shape. Rollback is a revert of this commit.

Related Issues

N/A

A -32603 reached the user as the bare line `error: [rpc -32603]
internal_error`, with nothing about what failed or where to look. The cause
was collected and then thrown away three times over:

- the dispatcher dropped `RpcError.detail` whenever the raiser also passed
  `data`, which `_build_tui_agent_loop` always does;
- `turn.send` emitted a latched init crash as `{code, message}` only, even
  though the ErrorEvent schema already has a `detail` field the front end
  renders;
- the client formatted `[rpc <code>] <message>`, where `message` is a fixed
  code name, and every call site prints `err.message`.

So an AgentLoop that cannot start (a config the running branch cannot parse,
for instance) produced an error that named neither the file nor the log.

Fold `detail` into `error.data` in a shared `error_data` helper used by both
the dispatcher and the turn events, pass the init-crash detail plus its
`log_path` into the emitted event, and build the client-side message from
`detail` / `exception_message` / `reason` with the log path appended. A
multi-line cause (a config error listing each offending field) keeps its
line breaks below the summary line.

Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.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