chenhg5/cc-connect · critical
codex app-server connection is closed
Error message
codex app-server connection is closed
What it means
`writeJSON` finds `s.stdin == nil` under procMu, meaning the child codex process's stdin was closed or never opened — the transport has been aborted (e.g. after a prior write timeout via abortTransport), the process exited, or Start failed. The fixed message `codex app-server connection is closed` is returned for any write attempted on a dead session.
Source
Thrown at agent/codex/appserver_session.go:1738
"method": method,
}
if params != nil {
payload["params"] = params
}
return s.writeJSON(payload)
}
func (s *appServerSession) writeJSON(v any) error {
b, err := json.Marshal(v)
if err != nil {
return fmt.Errorf("codex app-server encode: %w", err)
}
s.procMu.Lock()
stdin := s.stdin
s.procMu.Unlock()
if stdin == nil {
return fmt.Errorf("codex app-server connection is closed")
}
s.writeMu.Lock()
defer s.writeMu.Unlock()
if _, err := stdin.Write(append(b, '\n')); err != nil {
return fmt.Errorf("codex app-server write: %w", err)
}
return nil
}
View on GitHub (pinned to 4000b2338a)
Solutions
- Recreate the session via StartSession — the old one is unrecoverable once stdin is nil.
- Check StartSession's returned error before using the session; a failed start leaves no stdin.
- Inspect codex stderr/logs to find why the process exited (bad config, missing login).
- Serialize session access so calls don't race with Stop/abortTransport.
- Verify the codex binary launches successfully standalone with the same arguments.
Example fix
// before: reuse session after failure
sess.Send(msg)
// after: check liveness and rebuild
if !sess.Alive() {
sess, err = agent.StartSession(ctx, sessionOpts)
if err != nil { return err }
}
err = sess.Send(msg) Defensive patterns
Strategy: validation
Validate before calling
// check session liveness before every request
if !sess.Alive() {
return fmt.Errorf("codex session closed; call StartSession again")
} Try / catch
err := sess.request(method, params, out)
if err != nil && err.Error() == "codex app-server connection is closed" {
// unrecoverable — rebuild
if sess, err = agent.StartSession(ctx, opts); err != nil {
return err
}
return sess.request(method, params, out)
} Prevention
- Always check StartSession's error before using a session.
- Never reuse a session after Stop() or any write-timeout error.
- Monitor process exit so dead sessions are flagged before writes.
- Serialize lifecycle calls (Stop/Send) to avoid races.
When it happens
Trigger: Calling `request`, `requestWithTimeout`, or `notify` after the session was aborted (`abortTransport` set stdin=nil), after `Stop()`, after the codex process crashed, or before `Start()` completed successfully.
Common situations: Reusing a session object after a prior `write timed out` error killed it; codex process exited due to bad config/missing auth and callers keep sending turns; race between session teardown and an in-flight request; never calling Start or Start failing silently.
Understand the failure class
Background: ECONNREFUSED and "connection refused" / "could not connect to server" errors: what they mean and how to fix them — this error's family across 44 libraries.
Related errors
- codex app-server thread id is empty
- codex app-server turn/start: %w
- codex app-server turn/start returned empty turn id
- turn failed (no details)
- %s
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/897b45c7434116c9.
Report an issue: GitHub.