{"record":{"id":"3e549602928153bc","repo":"chenhg5/cc-connect","slug":"pisession-write-stdin-w","errorCode":null,"errorMessage":"piSession: write stdin: %w","messagePattern":"piSession: write stdin: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agent/pi/session.go","lineNumber":462,"sourceCode":"\treturn nil\n}\n\n// writeRPCCommand marshals cmd as a single JSONL line and writes it to the\n// RPC process's stdin under rpcStdinMu. Used by both sendRPC (for \"prompt\"\n// commands during a turn) and startRPC (for the startup \"get_state\" probe\n// that fetches the session id before callers are released).\nfunc (s *piSession) writeRPCCommand(cmd map[string]any) error {\n\tb, err := json.Marshal(cmd)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"piSession: marshal command: %w\", err)\n\t}\n\tb = append(b, '\\n')\n\n\ts.rpcStdinMu.Lock()\n\t_, err = s.rpcStdin.Write(b)\n\ts.rpcStdinMu.Unlock()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"piSession: write stdin: %w\", err)\n\t}\n\treturn nil\n}\n\n// sendRPC writes a JSON \"prompt\" command to the persistent RPC process stdin.\n// Events are read asynchronously by readLoopRPC, including agent_end which\n// triggers EventResult.\n//\n// Issue #1723: image paths are embedded into the message text as\n// @<path> references (pi's standard mechanism, parsed the same way as in\n// json mode). The rpc stdin pipe doesn't crash on NUL bytes, but the\n// model still needs to load the images from disk — embedding raw bytes\n// in the message field would give the model text-shaped garbage instead\n// of a real visual input.\n//\n// Issue #1767: filePaths (non-image attachments) are NOT @<path>'d.\n// pi's processFileArguments would inline their full UTF-8 contents into\n// the message text the model sees; we append a plain \"Files saved","sourceCodeStart":444,"sourceCodeEnd":480,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/agent/pi/session.go#L444-L480","documentation":"This error is produced by writeRPCCommand in agent/pi/session.go when writing a JSON-RPC command line to the stdin of the persistent pi RPC child process fails. The wrapped os.ErrClosed or broken-pipe error is included via %w, so the underlying cause is always the child's stdin pipe. The pi adapter keeps one long-lived RPC process per session and serializes all stdin writes through rpcStdinMu; this error means that channel to the child is unusable.","triggerScenarios":"Calling Send (via sendRPC) or starting the session (via startRPC) after the pi process has exited or its stdin pipe was closed; writes racing with Close(); the child crashing mid-session so the pipe is broken.","commonSituations":"The pi CLI binary crashed or was killed (OOM, signal) while the session object still looked alive; a turn was cancelled and teardown closed stdin while a send was in flight; a stale session reused after process exit.","solutions":["Check the wrapped cause with errors.Is(err, os.ErrClosed) / io.ErrClosedPipe to confirm the child process is gone","Recreate the pi session (start a new RPC process) instead of retrying the write","Log/inspect why the pi process exited (stderr, exit status) before restarting","Guard Send with a session-alive check so callers get a clearer 'session closed' error"],"exampleFix":"// before\nif err := session.Send(ctx, msg); err != nil {\n    return fmt.Errorf(\"send failed: %w\", err)\n}\n// after\nif err := session.Send(ctx, msg); err != nil {\n    if errors.Is(err, os.ErrClosed) || errors.Is(err, io.ErrClosedPipe) {\n        session, err = pi.NewSession(opts) // restart RPC process\n    }\n    return err\n}","handlingStrategy":"try-catch","validationCode":"// Go: check liveness before writing\nif !sessionAlive() {\n    return errors.New(\"pi session closed\")\n}","typeGuard":"func isPipeClosed(err error) bool {\n    return errors.Is(err, os.ErrClosed) || errors.Is(err, io.ErrClosedPipe) || errors.Is(err, syscall.EPIPE)\n}","tryCatchPattern":"if err := writeRPCCommand(cmd); err != nil {\n    if isPipeClosed(err) {\n        // child process is gone; restart session and resend once\n        return restartAndRetry(cmd)\n    }\n    return err\n}","preventionTips":["Check session/process liveness before each send","Resolve pending turns before calling Close/Stop","Monitor child exit status so you never write to a dead pipe","Retry once with a fresh session on pipe-closed errors"],"tags":["go","ipc","broken-pipe","process"],"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"}