chenhg5/cc-connect · error
write stdin: %w
Error message
write stdin: %w
What it means
writeJSON wraps a failure of cs.stdin.Write as 'write stdin: %w'. The claude process's stdin pipe rejected the write — the process has exited (broken pipe/EPIPE) or the pipe was closed during session teardown. This is the low-level failure behind 'the CLI died while I was sending it a message'.
Source
Thrown at agent/claudecode/session.go:1098
"request_id": requestID,
"response": permResponse,
},
}
slog.Debug("claudeSession: permission response", "request_id", requestID, "behavior", result.Behavior)
return cs.writeJSON(controlResponse)
}
func (cs *claudeSession) writeJSON(v any) error {
cs.stdinMu.Lock()
defer cs.stdinMu.Unlock()
data, err := json.Marshal(v)
if err != nil {
return fmt.Errorf("marshal: %w", err)
}
if _, err := cs.stdin.Write(append(data, '\n')); err != nil {
return fmt.Errorf("write stdin: %w", err)
}
return nil
}
func isClaudeEditTool(toolName string) bool {
switch toolName {
case "Edit", "Write", "NotebookEdit", "MultiEdit":
return true
default:
return false
}
}
func (cs *claudeSession) setPermissionMode(mode string) {
cs.permissionMode.Store(mode)
cs.autoApprove.Store(mode == "bypassPermissions")
cs.acceptEditsOnly.Store(mode == "acceptEdits")
cs.dontAsk.Store(mode == "dontAsk")View on GitHub (pinned to 4000b2338a)
Solutions
- Confirm the claude process is gone (`ps -p <pid>`); if so, recreate the session with StartSession and resend
- Handle EPIPE ('broken pipe' in the wrapped error) as 'session died' — mark the session dead and stop further Sends
- Check preceding EventError events for the CLI's stderr explaining the crash, and fix that root cause
- If writes race Close() in your code, serialize them with the session lifecycle (don't Send after calling Close)
Example fix
// before
if err := cs.writeJSON(msg); err != nil {
return err // broken pipe repeatedly retried
}
// after
if err := cs.writeJSON(msg); err != nil {
cs.alive.Store(false) // pipe dead → session dead
return fmt.Errorf("send to claude session failed, recreate session: %w", err)
} Defensive patterns
Strategy: try-catch
Validate before calling
// verify the process is still writable right before sending
func stdinWritable(pid int) bool {
return pid > 0 && syscall.Kill(pid, 0) == nil // ESRCH means process gone
} Try / catch
if err := sess.Send(prompt, msgID, nil, nil); err != nil {
var perr *net.OpError
if strings.Contains(err.Error(), "write stdin") || errors.Is(err, syscall.EPIPE) {
// broken pipe: process died mid-write
markSessionDead(sess)
sess = recreateSession()
err = sess.Send(prompt, msgID, nil, nil)
}
_ = perr
} Prevention
- Treat any 'write stdin' error as terminal for the session — never retry on the same pipe
- Consume the event feed so process death is noticed before writes race it
- Don't interleave Close()/Send() from different goroutines without lifecycle synchronization
- Keep the CLI healthy (auth, version) to minimize mid-conversation crashes
When it happens
Trigger: Send or RespondPermission passed the alive check, but the claude process exited just before/concurrently with the write (race), or the write raced Close() closing stdin; large payloads split across writes where the process died mid-write.
Common situations: CLI crash mid-conversation (auth expiry, OOM kill); user sends a message at the same moment the process exits; kill/SIGKILL during teardown; very long prompts hitting a dead process after a timeout.
Related errors
- read stdout: %w
- codex app-server stdin pipe: %w
- codex app-server stdout pipe: %w
- codex app-server stderr pipe: %w
- write rpc message: %w
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/57031494ae48e9a6.
Report an issue: GitHub.