{"record":{"id":"de1286a9375bfaa9","repo":"chenhg5/cc-connect","slug":"codex-app-server-start-returned-empty-thread-id","errorCode":null,"errorMessage":"codex app-server start returned empty thread id","messagePattern":"codex app-server start returned empty thread id","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agent/codex/appserver_session.go","lineNumber":348,"sourceCode":"\t\tvar resp threadResumeResponse\n\t\tif err := s.request(\"thread/resume\", params, &resp); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif resp.Thread.ID == \"\" {\n\t\t\treturn fmt.Errorf(\"codex app-server resume returned empty thread id\")\n\t\t}\n\t\ts.applyThreadRuntimeState(resp.Cwd, resp.Model, resp.ReasoningEffort)\n\t\ts.threadID.Store(resp.Thread.ID)\n\t\tslog.Info(\"codex app-server thread resumed\", \"thread_id\", resp.Thread.ID)\n\t\treturn nil\n\t}\n\n\tvar resp threadStartResponse\n\tif err := s.request(\"thread/start\", s.threadRequestParams(), &resp); err != nil {\n\t\treturn err\n\t}\n\tif resp.Thread.ID == \"\" {\n\t\treturn fmt.Errorf(\"codex app-server start returned empty thread id\")\n\t}\n\ts.applyThreadRuntimeState(resp.Cwd, resp.Model, resp.ReasoningEffort)\n\ts.threadID.Store(resp.Thread.ID)\n\tslog.Info(\"codex app-server thread started\", \"thread_id\", resp.Thread.ID)\n\treturn nil\n}\n\nfunc (s *appServerSession) threadRequestParams() map[string]any {\n\tparams := map[string]any{\n\t\t\"experimentalRawEvents\":  false,\n\t\t\"persistExtendedHistory\": false,\n\t}\n\tif model := s.GetModel(); model != \"\" {\n\t\tparams[\"model\"] = model\n\t}\n\tif approval, sandbox := appServerModeSettings(s.mode); approval != \"\" {\n\t\tparams[\"approvalPolicy\"] = approval\n\t\tif sandbox != \"\" {","sourceCodeStart":330,"sourceCodeEnd":366,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/agent/codex/appserver_session.go#L330-L366","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Capture/log the raw thread/start response and diff against threadStartResponse; fix the struct mapping.","Upgrade/downgrade the codex CLI so client and server agree on the schema.","Restart the app-server if it is returning degenerate empty threads.","Verify the request params (threadRequestParams) are acceptable to the server — some servers return empty threads on rejected-but-unreported params."],"exampleFix":"// before\nif resp.Thread.ID == \"\" {\n    return fmt.Errorf(\"codex app-server start returned empty thread id\")\n}\n// after\nif resp.Thread.ID == \"\" {\n    slog.Error(\"codex thread/start missing id\", \"raw\", rawRespBody)\n    return fmt.Errorf(\"codex app-server start returned empty thread id (check codex CLI version / protocol schema)\")\n}","handlingStrategy":"type-guard","validationCode":"// Validate the start response before storing the thread id\nif resp.Thread.ID == \"\" {\n    return errors.New(\"codex thread/start returned no id; check CLI version/protocol\")\n}","typeGuard":"func hasValidThread(resp threadStartResponse) bool {\n    return resp.Thread.ID != \"\"\n}","tryCatchPattern":"if err := session.Send(ctx, prompt); err != nil {\n    if strings.Contains(err.Error(), \"start returned empty thread id\") {\n        slog.Error(\"codex thread/start bad response; capture raw body and CLI version\")\n        // retry start once, then surface to user\n    }\n    return err\n}","preventionTips":["Pin the codex CLI version and re-run protocol integration tests on upgrades.","Log raw response payloads on schema-mismatch errors.","Restart the app-server if empty threads recur (degenerate server state).","Keep threadStartResponse struct in sync with upstream protocol docs."],"tags":["json-rpc","schema","codex","thread"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}