chenhg5/cc-connect · error
write rpc message: %w
Error message
write rpc message: %w
What it means
writeRPCMessage failed at the w.Write step: after successfully marshaling the JSON-RPC payload, writing the newline-delimited bytes to the codex app-server's stdin pipe failed, returning a wrapped "write rpc message" error. This means the pipe to the child process is broken — typically because the app-server process has exited or its stdin was closed.
Source
Thrown at agent/codex/session.go:782
}
}
func rpcNotifyOverIO(stdin io.Writer, method string, params any) error {
payload := map[string]any{
"jsonrpc": "2.0",
"method": method,
"params": params,
}
return writeRPCMessage(stdin, payload)
}
func writeRPCMessage(w io.Writer, payload any) error {
b, err := json.Marshal(payload)
if err != nil {
return fmt.Errorf("encode rpc message: %w", err)
}
if _, err := w.Write(append(b, '\n')); err != nil {
return fmt.Errorf("write rpc message: %w", err)
}
return nil
}
// RespondPermission is a no-op for Codex — permissions are handled via CLI flags.
func (cs *codexSession) RespondPermission(_ string, _ core.PermissionResult) error {
return nil
}
func (cs *codexSession) Events() <-chan core.Event {
return cs.events
}
func (cs *codexSession) CurrentSessionID() string {
v, _ := cs.threadID.Load().(string)
return v
}
View on GitHub (pinned to 4000b2338a)
Solutions
- Check whether the codex process is still alive (`ps aux | grep codex`); if it died, find its exit reason in logs/stderr and restart the session.
- Update the Codex CLI if it crashes during initialization — an early crash surfaces as a broken pipe on the next write.
- Avoid racing the session teardown: ensure Stop/session cleanup completes before issuing further RPCs.
- Retry the request with a fresh session; the old pipe cannot be revived.
- Check ulimits and OOM-killer logs if the process is being killed under memory pressure.
Example fix
// before: writing to a crashed app-server stdin
err := writeRPCMessage(stdin, payload) // broken pipe
// after: verify process health before RPC, else recreate
if cmd.ProcessState != nil && cmd.ProcessState.Exited() {
return nil, fmt.Errorf("codex app-server exited: %s", cmd.ProcessState)
} Defensive patterns
Strategy: try-catch
Validate before calling
// ensure the child process is still running before writing
if cmd.Process == nil || (cmd.ProcessState != nil && cmd.ProcessState.Exited()) {
return fmt.Errorf("codex app-server no longer running")
} Type guard
func isBrokenPipe(err error) bool {
return errors.Is(err, syscall.EPIPE) || errors.Is(err, io.ErrClosedPipe) || strings.Contains(err.Error(), "write rpc message")
} Try / catch
err := rpcRequestOverIO(stdin, stdout, ctx, method, params, &out)
if isBrokenPipe(err) {
slog.Warn("app-server pipe broken; restarting process")
// recreate the process/session and replay the request once
} Prevention
- Detect early app-server exit (wait + stderr capture) before issuing RPCs
- Serialize session teardown with in-flight RPCs (no writes after stdin.Close())
- Monitor child process health and restart sessions proactively on exit
- Check OOM-killer / ulimit logs when crashes recur under load
When it happens
Trigger: w.Write(append(b, '\n')) returns a non-nil error (EPIPE/broken pipe, or io.ErrClosedPipe) inside writeRPCMessage when rpcRequestOverIO or rpcNotifyOverIO sends a message to the app-server.
Common situations: The codex app-server crashed or exited mid-session and a subsequent RPC is written to its dead stdin; the probe process from loadCodexRuntimeConfig was killed by its deferred cleanup while a write was in flight; concurrent sessions closed the pipe.
Related errors
- write stdin: %w
- codex app-server initialized notify: %w
- codex app-server resume returned empty thread id
- codex app-server start returned empty thread id
- codex app-server turn/start: %w
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/bfcc35ebd0ea3e5d.
Report an issue: GitHub.