Skip to content

ACP adapter does not surface failed turns (non-auth failures resolve as end_turn, error only in session log) #1865

Description

@Jetiaime

What issue are you seeing?

The ACP adapter does not surface a failed turn to the client. When a turn fails for a non-auth reason (e.g. a non-retryable upstream HTTP 400, a rate limit, an expired token after retries are exhausted), packages/acp-adapter/src/session.ts handles turn.ended with reason: 'failed' by calling log.warn(...) and then resolving session/prompt with stopReason: 'end_turn' (only auth-coded failures are routed to a JSON-RPC error via authRequiredFromPayload). The actual error is written to the session log (~/.kimi-code/sessions/.../logs/kimi-code.log) but is not exposed on the ACP wire — not on the JSON-RPC error channel, not as refusal, and not in PromptResponse.

Consequence for ACP orchestration clients (Multica, OpenCode, Claude Code running kimi as a backend): a genuinely failed turn is indistinguishable from a clean, empty completion. The client reports the task as successfully completed with no output, and the actionable error stays buried in the local log.

Concrete case that motivated this report: a session accumulated an empty assistant message, so every subsequent prompt got 400 the message at position N with role 'assistant' must not be empty. kimi acp exited 0 with no output; the orchestrator recorded status=completed, output_bytes=0 on each attempt — a silent no-op the user experienced as "Kimi returns nothing".

Related: #1855 (usage not reported over ACP). Both are ACP-adapter observability gaps; this one is about failure visibility.

What steps can reproduce the bug?

  1. Drive kimi over ACP (kimi acp) from any ACP client.
  2. Force a non-auth, non-retryable turn failure — e.g. resume a session whose history contains an empty assistant message (server returns 400 ... must not be empty), or otherwise trigger a 4xx that exhausts retries.
  3. Observe: session/prompt resolves with stopReason: end_turn, no error is delivered to the client, and the failure appears only in logs/kimi-code.log (WARN llm request failed ... statusCode=400).

What is the expected behavior?

A non-auth turn failure should be observable by the ACP client without reading kimi's local log — e.g. surface the error on the JSON-RPC error channel, or attach the failure payload to PromptResponse (the spec provides _meta for out-of-band data), or otherwise signal that the turn did not complete cleanly. As-is, clients cannot tell a failed turn from an empty successful one.

Additional information

  • Version: Kimi Code 0.26.0
  • Source pointer: packages/acp-adapter/src/session.ts, the turn.ended handler — reason === 'failed' non-auth branch resolves { stopReason: turnEndReasonToStopReason(...) } = end_turn, per the documented rationale in events-map.ts (ACP StopReason has no failed variant, so the failure is only logged).
  • Downstream: ACP adapter does not report token usage (PromptResponse.usage empty, no usage_update emitted) #1855 (usage). Multica has landed a defensive daemon-side workaround (promote completed+empty+no-usage → failed), but the robust fix belongs here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions