chenhg5/cc-connect · error

codex app-server turn/start returned empty turn id

Error message

codex app-server turn/start returned empty turn id

What it means

The turn/start request succeeded (no transport error) but the response contained no turn id (resp.Turn.ID == ""). The library requires a turn id to track the current turn, correlate events, and support interruption, so an empty id is treated as an unusable response.

Source

Thrown at agent/codex/appserver_session.go:503

		"threadId": threadID,
		"input":    input,
	}
	if model := s.GetModel(); model != "" {
		params["model"] = model
	}
	if effort := s.GetReasoningEffort(); effort != "" {
		params["effort"] = effort
	}
	if approval, _ := appServerModeSettings(s.mode); approval != "" {
		params["approvalPolicy"] = approval
	}

	var resp turnStartResponse
	if err := s.request("turn/start", params, &resp); err != nil {
		return fmt.Errorf("codex app-server turn/start: %w", err)
	}
	if resp.Turn.ID == "" {
		return fmt.Errorf("codex app-server turn/start returned empty turn id")
	}

	s.stateMu.Lock()
	s.currentTurn = resp.Turn.ID
	s.pendingMsgs = s.pendingMsgs[:0]
	s.stateMu.Unlock()

	return nil
}

func (s *appServerSession) stageImages(prompt string, images []core.ImageAttachment) (string, []string, error) {
	if len(images) == 0 {
		return prompt, nil, nil
	}

	imgDir := filepath.Join(s.workDir, ".cc-connect", "images")
	if err := os.MkdirAll(imgDir, 0o755); err != nil {
		return "", nil, fmt.Errorf("codex app-server: create image dir: %w", err)

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Compare turnStartResponse field tags against the codex app-server protocol version in use and upgrade codex
  2. Log the raw turn/start JSON to confirm the shape the server actually returns
  3. Check codex server logs for why the turn failed to get an id
  4. Update the response struct / parser to the current protocol schema

Example fix

// before
type turnStartResponse struct {
    Turn struct { ID string `json:"id"` } `json:"turn"`
}
// after — log unexpected payloads for diagnosis
var raw json.RawMessage
req("turn/start", params, &raw)
if err := json.Unmarshal(raw, &resp); err != nil || resp.Turn.ID == "" {
    slog.Warn("codex turn/start unexpected payload", "payload", string(raw))
}
Defensive patterns

Strategy: validation

Validate before calling

raw, err := requestRaw("turn/start", params)
if err != nil { return err }
var resp turnStartResponse
if err := json.Unmarshal(raw, &resp); err != nil || resp.Turn.ID == "" {
    slog.Warn("unexpected turn/start payload", "payload", string(raw))
    return fmt.Errorf("turn/start: empty turn id")
}

Type guard

func validTurnStart(resp turnStartResponse) bool { return resp.Turn.ID != "" }

Prevention

When it happens

Trigger: The codex app-server returned a 200-style JSON-RPC result whose turn object is missing or has an empty id — typically a protocol/version mismatch or a server-side turn that failed to schedule.

Common situations: Running a codex binary whose turn/start response schema differs (older/newer protocol); the server accepted the request but silently dropped the turn; the turnStartResponse struct fields no longer match the server's JSON keys.

Understand the failure class

Background: "empty response", "returned no data", "empty embeddings": what HTTP 200-with-empty-body errors mean across libraries — this error's family across 36 libraries.

Related errors


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