Skip to content

fix(acp): surface agent stderr when the ACP process dies before initialize #84

Description

@64ix

Symptom

When an ACP agent process exits during startup, the renderer shows only:

Failed to load chat. ACP connection closed

Nothing tells the user (or the logs) why the agent died, even though the agent printed a precise, actionable error on stderr before exiting.

Incident (2026-08-06)

Launching an OpenCode conversation on a ProtoRTS worktree failed with the message above. Actual cause: the repo carried an invalid opencode.json (an extra mcp.servers nesting — opencode's schema has none), so every opencode acp spawn exited immediately with:

Error: Configuration is invalid at .../opencode.json
↳ Expected { "type": "local", ... } | { "type": "remote", ... }, got {...} mcp.servers

That message never surfaced anywhere. The app log only had:

error: "ACP connection closed"   msg: "createAcpAgentConnection: initialize failed"
error: "[object Object]"         msg: "AcpConnectionSource: provisioning failed"

Diagnosing it required querying the app DB for the workspace path and replaying the JSON-RPC handshake by hand.

Root causes

  1. stderr is dropped. packages/runtime/src/acp-agents/connection/acp-agent-connection.ts (~line 77): agent stderr is logged at debug only and never attached to the error, so in a packaged build it vanishes.
  2. Opaque error on early exit. When the process dies before initialize completes, the error that wins the race and reaches the renderer's AcpStartError is the SDK's generic "ACP connection closed" — no exit code, no stderr. (failClosedBeforeReady does build a message with the exit code, but it can lose the race against the SDK connection error, and it still has no stderr either way.)
  3. Broken log serialization. packages/runtime/src/acp-agents/connection/source.ts:71: String(error) on an AcpConnectionError plain object prints [object Object].

Proposed fix

  • Keep a small bounded ring buffer of the agent's recent stderr in createAcpAgentConnection.
  • On close-before-ready (and on initialize failure generally), build the InitializeFailedError from exit code + last stderr lines, and make sure that detail propagates through AcpConnectionSource → RPC → renderer AcpStartError, so the chat panel error state can show the real reason.
  • Serialize the error properly in the provisioning failed log line (it is a {type, ...} object, not an Error).

Repro

  1. In any project, create an opencode.json with the invalid nesting: {"mcp": {"servers": {"x": {"type": "local", "command": ["true"]}}}}
  2. Open an OpenCode (ACP) conversation on a task in that project.
  3. Observe "Failed to load chat. ACP connection closed" with no mention of the config error that opencode printed on stderr.

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triageMaintainer needs to evaluate

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions