MoonshotAI / MoonshotAI/kimi-code

Headless --prompt stream-json: final assistant message + session.resume_hint can be lost (stdout backpressure ignored, process.exit on signal discards undrained pipe)

Open
#1,897 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
7.5k
Forks
1.2k
Avg merge
11h 53m
Merged PRs (30d)
350

Description

Environment

kimi-code 0.27.0 (Homebrew, Linux), bundled Node v26.5.0, driven headlessly by a bridge (chenhg5/cc-connect) as: kimi --output-format stream-json --prompt <text>, stdout = pipe.

Symptom

On longer multi-tool turns (e.g. 3-4 sequential Read calls with large results), the consumer receives the tool_call/tool events but the stream ends WITHOUT the final {"role":"assistant","content":"..."} line and WITHOUT the {"role":"meta","type":"session.resume_hint",...} trailer. Short turns (0-1 tools) are fine. The session transcript on disk (agents/main/wire.jsonl) always contains the final assistant message and finishReason:"end_turn" — the answer is generated and persisted, only the stdout emission is lost.

Root cause (from reading the 0.27.0 bundle, dist/main.mjs)

  1. The headless JSON writer does not honor stdout backpressure: PromptJsonWriter.writeJsonLine (~line 430351) and writeResumeHint (~430206) call stdout.write(...) and ignore the return value; the waitForDrain/drainStdout helpers that exist elsewhere in the bundle are never used on this path.
  2. The prompt-mode signal handler (installPromptTerminationCleanupexitAfterCleanup, ~431087) ends with process.exit(...), which discards any un-drained stdout buffer. Cleanup is time-bounded (PROMPT_CLEANUP_TIMEOUT_MS=8000 / CLI_SHUTDOWN_TIMEOUT_MS=3000) but never flushes the pipe.

So when the consumer is slow to drain (big tool-result lines back up the pipe) and the process is terminated by signal after the turn logically completes, exactly the two trailing writes — the final assistant text and the resume hint — are lost. The consumer then sees an "empty" reply and (because the resume hint is gone) also loses session continuity.

Suggested fix

Await drain (or use a write callback / stream.finished) in writeJsonLine/writeResumeHint, and prefer process.exitCode + natural exit (or an explicit stdout flush) over process.exit() in the prompt-mode signal path.

Repro sketch

  1. kimi --output-format stream-json --prompt "read these four files then reply DONE" with stdout piped to a slow consumer (or a consumer that SIGTERMs on its own timeout).
  2. Compare stdout capture vs the session's wire.jsonl: wire.jsonl has the final assistant message; stdout ends at the last tool event.

🤖 Generated with Claude Code

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in dist/main.mjs at PromptJsonWriter.writeJsonLine, writeResumeHint, and the installPromptTerminationCleanup/exitAfterCleanup signal path. Reproduce with stream-json output piped to a slow consumer and compare stdout with agents/main/wire.jsonl. Done means the final assistant line and session.resume_hint remain in stdout under backpressure and signal cleanup.

Written by the indexing model from the issue text.

Assessment

Tech stack
node.js, typescript
Domain
cli
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
58/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.