mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix(aibridge): recognize hyphenated session-id header from newer Codex releases (#26346)
Codex prompts were showing up as one session per request in the AI Sessions list instead of being grouped into a conversation. ## Root Cause AI Gateway extracts the Codex session key from the `session_id` request header: https://github.com/coder/coder/blob/main/aibridge/session.go#L57-L58 Newer Codex releases renamed the header to `session-id` (hyphen) in [`codex-rs/codex-api/src/requests/headers.rs`](https://github.com/openai/codex/blob/main/codex-rs/codex-api/src/requests/headers.rs): ```rust insert_header(&mut headers, "session-id", &id); ``` `Header.Get` is case-insensitive but not underscore/hyphen-insensitive, so no session key is extracted and every request falls back to its own session. Reproduced with Codex CLI 0.139.0. ## Changes - Check `session-id` first, fall back to the legacy `session_id` for older Codex versions - Added test cases for the hyphenated header and precedence ## Before/After The same three-prompt Codex conversation ("Write a haiku about Pittsburgh" → "Now make it about Coder" → "Translate it to Spanish", via `codex exec` + `codex exec resume --last`) against a local build. **Before**: each prompt of the conversation lands as its own session, Threads: 1  **After**: the conversation is a single session with Threads: 3  Clicking into the session shows all three threads on the session timeline:  Linear: [AIGOV-437](https://linear.app/codercom/issue/AIGOV-437) 🤖 Generated with Coder Agents on behalf of @bpmct
This commit is contained in:
@@ -55,6 +55,12 @@ func GuessSessionID(client Client, r *http.Request) *string {
|
||||
}
|
||||
return cleanRef(matches[1])
|
||||
case ClientCodex:
|
||||
// Codex renamed the header from "session_id" to "session-id" in
|
||||
// newer releases. Check the current name first, then fall back to
|
||||
// the legacy name for older Codex versions.
|
||||
if sid := cleanRef(r.Header.Get("session-id")); sid != nil {
|
||||
return sid
|
||||
}
|
||||
return cleanRef(r.Header.Get("session_id"))
|
||||
case ClientMux:
|
||||
return cleanRef(r.Header.Get("X-Mux-Workspace-Id"))
|
||||
|
||||
@@ -105,6 +105,18 @@ func TestGuessSessionID(t *testing.T) {
|
||||
headers: map[string]string{"session_id": "codex-session-123"},
|
||||
sessionID: utils.PtrTo("codex-session-123"),
|
||||
},
|
||||
{
|
||||
name: "codex_with_hyphenated_session_header",
|
||||
client: aibridge.ClientCodex,
|
||||
headers: map[string]string{"session-id": "codex-session-456"},
|
||||
sessionID: utils.PtrTo("codex-session-456"),
|
||||
},
|
||||
{
|
||||
name: "codex_hyphenated_header_takes_precedence",
|
||||
client: aibridge.ClientCodex,
|
||||
headers: map[string]string{"session-id": "codex-session-new", "session_id": "codex-session-old"},
|
||||
sessionID: utils.PtrTo("codex-session-new"),
|
||||
},
|
||||
{
|
||||
name: "codex_with_whitespace_in_header",
|
||||
client: aibridge.ClientCodex,
|
||||
|
||||
Reference in New Issue
Block a user