chenhg5/cc-connect · error

codex app-server start returned empty thread id

Error message

codex app-server start returned empty thread id

What it means

This error is returned when starting a fresh codex thread: the "thread/start" JSON-RPC request succeeded but the response carried an empty thread id. Since all later turns depend on the thread id, the library refuses to continue with an empty value. It usually signals protocol/schema drift or a degraded app-server response.

Source

Thrown at agent/codex/appserver_session.go:348

		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 nil
}

func (s *appServerSession) threadRequestParams() map[string]any {
	params := map[string]any{
		"experimentalRawEvents":  false,
		"persistExtendedHistory": false,
	}
	if model := s.GetModel(); model != "" {
		params["model"] = model
	}
	if approval, sandbox := appServerModeSettings(s.mode); approval != "" {
		params["approvalPolicy"] = approval
		if sandbox != "" {

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Capture/log the raw thread/start response and diff against threadStartResponse; fix the struct mapping.
  2. Upgrade/downgrade the codex CLI so client and server agree on the schema.
  3. Restart the app-server if it is returning degenerate empty threads.
  4. Verify the request params (threadRequestParams) are acceptable to the server — some servers return empty threads on rejected-but-unreported params.

Example fix

// before
if resp.Thread.ID == "" {
    return fmt.Errorf("codex app-server start returned empty thread id")
}
// after
if resp.Thread.ID == "" {
    slog.Error("codex thread/start missing id", "raw", rawRespBody)
    return fmt.Errorf("codex app-server start returned empty thread id (check codex CLI version / protocol schema)")
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Validate the start response before storing the thread id
if resp.Thread.ID == "" {
    return errors.New("codex thread/start returned no id; check CLI version/protocol")
}

Type guard

func hasValidThread(resp threadStartResponse) bool {
    return resp.Thread.ID != ""
}

Try / catch

if err := session.Send(ctx, prompt); err != nil {
    if strings.Contains(err.Error(), "start returned empty thread id") {
        slog.Error("codex thread/start bad response; capture raw body and CLI version")
        // retry start once, then surface to user
    }
    return err
}

Prevention

When it happens

Trigger: Calling Send (with no resumeID) on a codex session; the app-server responds to thread/start but resp.Thread.ID is "" — server returned an unexpected shape or an empty thread object.

Common situations: Codex CLI upgrade changed thread/start response schema; app-server in a bad state (e.g. failed model backend) returning an empty thread; client struct fields misaligned with new JSON keys.

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


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/de1286a9375bfaa9. Report an issue: GitHub.