router-for-me/CLIProxyAPI · warning
Codex live call request requires an SDP offer
Error message
Codex live call request requires an SDP offer
What it means
The HTTP stream bridge found no entry for the given id: either no stream was ever opened with it, or the stream was already closed and removed. Reads on unknown/expired IDs return this error (with done=true) instead of blocking.
Source
Thrown at internal/client/codex/live/live.go:691
payload := struct {
SDP string `json:"sdp"`
Session json.RawMessage `json:"session,omitempty"`
}{
SDP: sdp,
Session: session,
}
encoded, errMarshal := json.Marshal(payload)
if errMarshal != nil {
return nil, fmt.Errorf("failed to encode Codex live request: %w", errMarshal)
}
return encoded, nil
}
func callRequestSDP(body []byte, contentType string) (string, error) {
mediaType, _, errMediaType := mime.ParseMediaType(contentType)
if errMediaType == nil && (strings.EqualFold(mediaType, "application/sdp") || strings.EqualFold(mediaType, "text/plain")) {
if strings.TrimSpace(string(body)) == "" {
return "", errors.New("Codex live call request requires an SDP offer")
}
return string(body), nil
}
if errMediaType != nil || !strings.EqualFold(mediaType, "application/json") {
return "", errors.New("Codex live media relay requires an SDP or JSON call request")
}
var payload struct {
SDP string `json:"sdp"`
}
if errUnmarshal := json.Unmarshal(body, &payload); errUnmarshal != nil {
return "", fmt.Errorf("failed to decode Codex live call request: %w", errUnmarshal)
}
if strings.TrimSpace(payload.SDP) == "" {
return "", errors.New("Codex live call request requires an SDP offer")
}
return payload.SDP, nil
}
View on GitHub (pinned to 78f0c4079e)
Solutions
- Stop reading once a read returns Done=true or an error; treat unknown-stream as terminal, not retryable.
- Close over the read loop so only one goroutine consumes a given StreamID.
- Drop all stream IDs on host reconnection/restart and re-open streams as needed.
Example fix
// after: treat 'not open' as terminal
chunk, done, err := read(ctx, streamID)
if err != nil {
if strings.Contains(err.Error(), "is not open") {
return nil // stream already finished/elsewhere closed; stop cleanly
}
return err
} Defensive patterns
Strategy: try-catch
Try / catch
chunk, done, err := bridge.Read(ctx, id)
if err != nil {
if strings.Contains(err.Error(), "is not open") {
return nil // stream already closed/finished elsewhere — treat as terminal
}
if errors.Is(err, context.Canceled) {
return err
}
return err
}
if done { break } Prevention
- Stop reading on Done=true or any error; never re-read a closed stream.
- Single consumer per stream ID.
- Invalidate all stream IDs after host restarts since the bridge is in-memory.
When it happens
Trigger: Calling read after close; reading an ID from a previous host process (the bridge is in-memory); double-closing then reading; using an ID after the stream ended naturally (bridge auto-closes on channel close or ctx cancellation).
Common situations: Plugin retry logic that re-reads after a completed stream; host restart invalidating all IDs while the plugin keeps state; races between a consumer goroutine finishing the stream and another goroutine issuing one more read.
Related errors
- Realtime client secrets require an SDP or JSON call request
- Codex live multipart body requires an sdp field
- count must be a positive integer
- Codex Alpha Search API key base URL unavailable
- invalid_realtime_client_secret
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/f1343af49aae43cc.
Report an issue: GitHub.