chenhg5/cc-connect · error
codex app-server resume returned empty thread id
Error message
codex app-server resume returned empty thread id
What it means
This error is returned when resuming an existing codex thread: the "thread/resume" JSON-RPC request succeeded but its response contained an empty thread id. The library treats a resume without an id as invalid because every subsequent turn must reference the thread id. It guards against protocol drift where the response shape changed (e.g. id moved to a different field).
Source
Thrown at agent/codex/appserver_session.go:335
}
if err := s.notify("initialized", nil); err != nil {
return fmt.Errorf("codex app-server initialized notify: %w", err)
}
return nil
}
func (s *appServerSession) ensureThread(resumeID string) error {
if resumeID != "" && resumeID != core.ContinueSession {
params := s.threadRequestParams()
params["threadId"] = resumeID
params["persistExtendedHistory"] = true
var resp threadResumeResponse
if err := s.request("thread/resume", params, &resp); err != nil {
return err
}
if resp.Thread.ID == "" {
return fmt.Errorf("codex app-server resume returned empty thread id")
}
s.applyThreadRuntimeState(resp.Cwd, resp.Model, resp.ReasoningEffort)
s.threadID.Store(resp.Thread.ID)
slog.Info("codex app-server thread resumed", "thread_id", resp.Thread.ID)
return nil
}
var resp threadStartResponse
if err := s.request("thread/start", s.threadRequestParams(), &resp); err != nil {
return err
}
if resp.Thread.ID == "" {
return fmt.Errorf("codex app-server start returned empty thread id")
}
s.applyThreadRuntimeState(resp.Cwd, resp.Model, resp.ReasoningEffort)
s.threadID.Store(resp.Thread.ID)
slog.Info("codex app-server thread started", "thread_id", resp.Thread.ID)
return nilView on GitHub (pinned to 4000b2338a)
Solutions
- Compare the actual thread/resume JSON against the threadResumeResponse struct; update the struct to the current codex protocol.
- Log the raw response body on this error path to see the real shape.
- Start a new thread (drop the stale resume id) if the server no longer knows it.
- Pin/downgrade the codex CLI to a version matching the expected response schema.
- Verify the resumeID being passed is a valid id previously returned by thread/start, not a user-supplied session label.
Example fix
// before
type threadResumeResponse struct {
Thread struct{ ID string `json:"id"` } `json:"thread"`
}
// after — after inspecting the raw server response for schema drift:
type threadResumeResponse struct {
Thread struct {
ID string `json:"id"`
} `json:"thread"`
// fallback for servers that return the id at top level
ThreadID string `json:"threadId"`
}
func (r threadResumeResponse) id() string {
if r.Thread.ID != "" { return r.Thread.ID }
return r.ThreadID
} Defensive patterns
Strategy: type-guard
Validate before calling
// Before resuming, verify the id is a thread id previously returned by the app-server
if resumeID == "" || !isValidThreadID(resumeID) {
return errors.New("invalid or missing thread id for resume; start a new thread")
} Type guard
func hasValidThread(resp threadResumeResponse) bool {
return resp.Thread.ID != ""
}
if !hasValidThread(resp) {
slog.Warn("thread/resume returned no id; starting fresh thread")
return s.ensureThread("")
} Try / catch
if err := session.Send(ctx, prompt); err != nil {
if strings.Contains(err.Error(), "resume returned empty thread id") {
// schema drift or stale id: fall back to a new thread
session, err = agent.StartSession(ctx, core.StartOptions{})
}
return err
} Prevention
- Log raw JSON-RPC responses so schema drift is immediately visible.
- Run integration tests against the pinned codex CLI version on every upgrade.
- Treat empty ids as fall-back-to-new-thread rather than hard failure in user flows.
- Never feed user-facing session labels in as thread ids.
When it happens
Trigger: Calling Send/SendWithResume on a codex session with a non-empty resumeID; the app-server answers thread/resume but resp.Thread.ID is "" — either the server returned a malformed/degraded response or the client's threadResumeResponse struct no longer matches the server's JSON shape.
Common situations: Codex CLI upgrade changed the resume response schema (thread id relocated/renamed); resuming a thread id that the server silently cannot resolve and returns an empty object for; corrupted session store on the app-server side.
Understand the failure class
Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.
Related errors
- codex app-server start returned empty thread id
- codex app-server turn/start: %w
- turn failed (no details)
- %s
- %s timed out
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/8341290ab6c997fb.
Report an issue: GitHub.