{"record":{"id":"cf458b190dd150e4","repo":"chenhg5/cc-connect","slug":"read-stdout-w-cf458b","errorCode":null,"errorMessage":"read stdout: %w","messagePattern":"read stdout: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agent/kimi/session.go","lineNumber":296,"sourceCode":"\t// never sees EventError after EventResult from the same turn.\n\twaitErr := cmd.Wait()\n\n\t// Kimi writes \"To resume this session: kimi -r <uuid>\" to stderr (not stdout),\n\t// so the scanner above never sees it. Extract it from the captured stderr\n\t// buffer before emitting EventResult so the next turn can pass --resume.\n\tfor _, line := range strings.Split(stderrBuf.String(), \"\\n\") {\n\t\tif strings.HasPrefix(strings.TrimSpace(line), \"To resume this session:\") {\n\t\t\tif id := extractResumeSessionID(line); id != \"\" {\n\t\t\t\tks.sessionID.Store(id)\n\t\t\t\tslog.Debug(\"kimiSession: session id from stderr\", \"session_id\", id)\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif scanErr != nil {\n\t\tslog.Error(\"kimiSession: scanner error\", \"error\", scanErr)\n\t\tevt := core.Event{Type: core.EventError, Error: fmt.Errorf(\"read stdout: %w\", scanErr)}\n\t\tselect {\n\t\tcase ks.events <- evt:\n\t\tcase <-ks.ctx.Done():\n\t\t\treturn\n\t\t}\n\t}\n\n\tif waitErr != nil {\n\t\tstderrMsg := strings.TrimSpace(stderrBuf.String())\n\t\tif stderrMsg != \"\" {\n\t\t\tslog.Error(\"kimiSession: process failed\", \"error\", waitErr, \"stderr\", stderrMsg)\n\t\t\tevt := core.Event{Type: core.EventError, Error: fmt.Errorf(\"%s\", stderrMsg)}\n\t\t\tselect {\n\t\t\tcase ks.events <- evt:\n\t\t\tcase <-ks.ctx.Done():\n\t\t\t\treturn\n\t\t\t}\n\t\t\treturn","sourceCodeStart":278,"sourceCodeEnd":314,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/agent/kimi/session.go#L278-L314","documentation":"The kimi session's readLoop scans the CLI process's stdout with bufio.Scanner; if scanning aborts with an error (not a clean EOF), it logs 'scanner error' and emits core.Event{Type: EventError, Error: fmt.Errorf(\"read stdout: %w\", scanErr)} on the session's Events channel. This error is asynchronous — the caller receives it as an event, not as a Send() return value.","triggerScenarios":"The kimi process dies or its stdout pipe breaks mid-stream (crash, kill, context cancellation closing the pipe); stdout emits a single line exceeding bufio.Scanner's default 64KB token limit (bufio.ErrTooLong); an OS-level I/O error reading the pipe.","commonSituations":"kimi CLI crashing while printing one very large JSON line (big diffs easily exceed 64KB — hits the default Scanner limit); the CLI being OOM-killed mid-response; container shutdown terminating the process.","solutions":["Inspect the wrapped cause: if it is bufio.ErrTooLong, enlarge the scanner buffer in agent/kimi/session.go (scanner.Buffer).","Check journalctl/dmesg for OOM kills of the kimi process and raise memory limits.","Consume the session's Events() channel; on EventError 'read stdout', recreate the session and retry the turn.","Reproduce manually: `kimi --prompt \"...\"` in the session workDir to see why stdout aborts."],"exampleFix":"// before (agent/kimi/session.go readLoop)\nscanner := bufio.NewScanner(stdout)\n\n// after\nscanner := bufio.NewScanner(stdout)\nscanner.Buffer(make([]byte, 0, 64*1024), 10*1024*1024) // allow 10MB lines","handlingStrategy":"retry","validationCode":"// no pre-call validation possible for async stream errors; watch events instead\ngo func() {\n    for evt := range sess.Events() {\n        if evt.Type == core.EventError {\n            slog.Warn(\"kimi stream error\", \"err\", evt.Error)\n        }\n    }\n}()","typeGuard":"func isReadStdoutError(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"read stdout:\")\n}","tryCatchPattern":"for evt := range sess.Events() {\n    if evt.Type == core.EventError && isReadStdoutError(evt.Error) {\n        // recreate session and replay the last prompt\n        sess, _ = agent.StartSession(ctx, opts)\n        _ = sess.Send(lastPrompt, msgID, nil, nil)\n    }\n}","preventionTips":["Watch for bufio.ErrTooLong in the wrapped error — big single-line CLI output needs a larger Scanner buffer","Give containers enough memory so the CLI is not OOM-killed mid-response","Always consume Events(); unhandled errors look like silent dead sessions","Pin a stable kimi CLI version known not to crash on large outputs"],"tags":["go","process","stdout","streaming"],"backgroundTag":"broken-pipe","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}