{"record":{"id":"dfda9bfe093eedca","repo":"chenhg5/cc-connect","slug":"codex-app-server-thread-id-is-empty","errorCode":null,"errorMessage":"codex app-server thread id is empty","messagePattern":"codex app-server thread id is empty","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agent/codex/appserver_session.go","lineNumber":468,"sourceCode":"\t\tfilePaths := core.SaveFilesToDisk(s.workDir, messageID, files)\n\t\tprompt = core.AppendFileRefs(prompt, filePaths)\n\t}\n\n\tprompt, imagePaths, err := s.stageImages(prompt, images)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.stateMu.Lock()\n\tif !s.preambleSent {\n\t\tprompt = prependCodexPromptPreamble(prompt, s.promptPreamble)\n\t\ts.preambleSent = true\n\t}\n\ts.stateMu.Unlock()\n\n\tthreadID := s.CurrentSessionID()\n\tif threadID == \"\" {\n\t\treturn fmt.Errorf(\"codex app-server thread id is empty\")\n\t}\n\n\tinput := make([]map[string]any, 0, 1+len(imagePaths))\n\tinput = append(input, map[string]any{\n\t\t\"type\":          \"text\",\n\t\t\"text\":          prompt,\n\t\t\"text_elements\": []any{},\n\t})\n\tfor _, path := range imagePaths {\n\t\tinput = append(input, map[string]any{\n\t\t\t\"type\": \"localImage\",\n\t\t\t\"path\": path,\n\t\t})\n\t}\n\n\tparams := map[string]any{\n\t\t\"threadId\": threadID,\n\t\t\"input\":    input,","sourceCodeStart":450,"sourceCodeEnd":486,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/agent/codex/appserver_session.go#L450-L486","documentation":"The codex app-server session attempted to start a turn but the session has no thread ID (CurrentSessionID() returned an empty string). The thread ID is established when the app-server thread is created/resumed; sending input without it means the session was never initialized, so the library fails fast instead of sending an unusable turn/start request.","triggerScenarios":"Calling Send (or SendWithImages) on an appServerSession whose thread/create or thread/resume handshake never completed or failed silently, so s.currentSessionID was never populated.","commonSituations":"Starting a session against an older codex binary that does not support app-server thread creation; the thread/create response was malformed; manually constructing an appServerSession instead of going through the agent's StartSession; a race where Send is called before the session finished initializing.","solutions":["Check that the codex CLI binary supports the app-server protocol (upgrade codex to a current version)","Recreate the session via the agent's StartSession so thread/create runs and sets the session ID","Log/check the thread/create response for a missing id and fix the handshake path","Verify no code path resets s.currentSessionID to empty after initialization"],"exampleFix":"// before\ns := &appServerSession{} // never went through thread/create\ns.Send(ctx, prompt, nil)\n// after\nsess, err := agent.StartSession(ctx, sessionOpts) // performs thread/create and sets the thread id\nif err != nil { return err }\nsess.Send(ctx, prompt, nil)","handlingStrategy":"validation","validationCode":"if sess == nil || sess.CurrentSessionID() == \"\" {\n    return fmt.Errorf(\"session not initialized: no thread id\")\n}\nsess.Send(ctx, prompt, nil)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always create sessions via agent.StartSession, never by constructing the session type directly","Check IsAlive()/session state before sending after long idle periods","Log the thread id at session start so empty-id states are caught early"],"tags":["codex","app-server","session-state"],"backgroundTag":"empty-required-field","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"}