MoonshotAI / MoonshotAI/kimi-code
ACP adapter does not surface failed turns (non-auth failures resolve as end_turn, error only in session log)
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 7.5k
- Forks
- 1.2k
- Avg merge
- 11h 53m
- Merged PRs (30d)
- 350
Description
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?
- Drive kimi over ACP (
kimi acp) from any ACP client. - Force a non-auth, non-retryable turn failure — e.g. resume a session whose history contains an empty
assistantmessage (server returns400 ... must not be empty), or otherwise trigger a 4xx that exhausts retries. - Observe:
session/promptresolves withstopReason: end_turn, no error is delivered to the client, and the failure appears only inlogs/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, theturn.endedhandler —reason === 'failed'non-auth branch resolves{ stopReason: turnEndReasonToStopReason(...) }=end_turn, per the documented rationale inevents-map.ts(ACPStopReasonhas nofailedvariant, so the failure is only logged). - Downstream: MoonshotAI/kimi-code#1855 (usage). Multica has landed a defensive daemon-side workaround (promote completed+empty+no-usage → failed), but the robust fix belongs here.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in packages/acp-adapter/src/session.ts at the turn.ended handler, then read the documented rationale in events-map.ts for StopReason mapping. Reproduce the failure with kimi acp using an empty assistant message or an exhausted non-auth retry, and verify that the ACP client can distinguish the failed turn from a clean empty completion without reading kimi-code.log.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100