{"record":{"id":"57031494ae48e9a6","repo":"chenhg5/cc-connect","slug":"write-stdin-w","errorCode":null,"errorMessage":"write stdin: %w","messagePattern":"write stdin: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agent/claudecode/session.go","lineNumber":1098,"sourceCode":"\t\t\t\"request_id\": requestID,\n\t\t\t\"response\":   permResponse,\n\t\t},\n\t}\n\n\tslog.Debug(\"claudeSession: permission response\", \"request_id\", requestID, \"behavior\", result.Behavior)\n\treturn cs.writeJSON(controlResponse)\n}\n\nfunc (cs *claudeSession) writeJSON(v any) error {\n\tcs.stdinMu.Lock()\n\tdefer cs.stdinMu.Unlock()\n\n\tdata, err := json.Marshal(v)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"marshal: %w\", err)\n\t}\n\tif _, err := cs.stdin.Write(append(data, '\\n')); err != nil {\n\t\treturn fmt.Errorf(\"write stdin: %w\", err)\n\t}\n\treturn nil\n}\n\nfunc isClaudeEditTool(toolName string) bool {\n\tswitch toolName {\n\tcase \"Edit\", \"Write\", \"NotebookEdit\", \"MultiEdit\":\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc (cs *claudeSession) setPermissionMode(mode string) {\n\tcs.permissionMode.Store(mode)\n\tcs.autoApprove.Store(mode == \"bypassPermissions\")\n\tcs.acceptEditsOnly.Store(mode == \"acceptEdits\")\n\tcs.dontAsk.Store(mode == \"dontAsk\")","sourceCodeStart":1080,"sourceCodeEnd":1116,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/agent/claudecode/session.go#L1080-L1116","documentation":"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'.","triggerScenarios":"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.","commonSituations":"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.","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)"],"exampleFix":"// before\nif err := cs.writeJSON(msg); err != nil {\n    return err // broken pipe repeatedly retried\n}\n// after\nif err := cs.writeJSON(msg); err != nil {\n    cs.alive.Store(false) // pipe dead → session dead\n    return fmt.Errorf(\"send to claude session failed, recreate session: %w\", err)\n}","handlingStrategy":"try-catch","validationCode":"// verify the process is still writable right before sending\nfunc stdinWritable(pid int) bool {\n    return pid > 0 && syscall.Kill(pid, 0) == nil // ESRCH means process gone\n}","typeGuard":null,"tryCatchPattern":"if err := sess.Send(prompt, msgID, nil, nil); err != nil {\n    var perr *net.OpError\n    if strings.Contains(err.Error(), \"write stdin\") || errors.Is(err, syscall.EPIPE) {\n        // broken pipe: process died mid-write\n        markSessionDead(sess)\n        sess = recreateSession()\n        err = sess.Send(prompt, msgID, nil, nil)\n    }\n    _ = perr\n}","preventionTips":["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"],"tags":["go","pipe","broken-pipe","process-lifecycle"],"backgroundTag":"broken-pipe","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}