router-for-me/CLIProxyAPI · error
Codex live multipart body requires an sdp field
Error message
Codex live multipart body requires an sdp field
What it means
The HTTP stream bridge's read rejects a call whose stream id is empty (or the bridge receiver is nil). IDs are minted by open() as incrementing decimal counters, so an empty id means the caller never obtained one — it is a programming error in the RPC sequence, not a runtime race.
Source
Thrown at internal/client/codex/live/live.go:659
}
if errClose != nil {
return nil, "", "", fmt.Errorf("failed to close Codex live multipart field: %w", errClose)
}
switch part.FormName() {
case "sdp":
value := string(partBody)
sdp = &value
case "session":
if !json.Valid(partBody) {
return nil, "", "", errors.New("Codex live session field must contain valid JSON")
}
session = append(json.RawMessage(nil), partBody...)
model = modelFromJSON(partBody)
}
}
if sdp == nil {
return nil, "", "", errors.New("Codex live multipart body requires an sdp field")
}
if model == "" {
model = defaultLiveModel
}
encoded, errEncode := encodeCallRequest(*sdp, session)
if errEncode != nil {
return nil, "", "", errEncode
}
return encoded, "application/json", model, nil
}
func encodeCallRequest(sdp string, session json.RawMessage) ([]byte, error) {
payload := struct {
SDP string `json:"sdp"`
Session json.RawMessage `json:"session,omitempty"`
}{
SDP: sdp,View on GitHub (pinned to 78f0c4079e)
Solutions
- Always check the error from the call that returns the StreamID before issuing reads.
- Fail fast in the plugin if the returned StreamID is empty.
- Keep the stream lifecycle (open → read* → close) in one function so the ID cannot be lost.
Example fix
// before
resp, _ := host.Call(ctx, streamOpenMethod, raw) // error ignored
readReq, _ := json.Marshal(pluginapi.HTTPStreamReadRequest{StreamID: resp.StreamID}) // "" if resp failed
// after
respRaw, err := host.Call(ctx, streamOpenMethod, raw)
if err != nil {
return err
}
var resp pluginapi.HTTPStreamResponse
if err := json.Unmarshal(respRaw, &resp); err != nil || resp.StreamID == "" {
return fmt.Errorf("no stream id from open: %v", err)
} Defensive patterns
Strategy: validation
Validate before calling
if streamID == "" {
return errors.New("http stream id is required")
} Type guard
func hasStreamID(id string) bool { return strings.TrimSpace(id) != "" } Try / catch
_, _, err := bridge.Read(ctx, id)
if err != nil && strings.Contains(err.Error(), "stream id is required") {
return errors.New("programming error: read called without an open stream")
} Prevention
- Check the open-call error and non-empty StreamID before any read.
- Scope the stream ID to the function that opened the stream.
- Fail fast on empty IDs instead of sending them across the RPC boundary.
When it happens
Trigger: Plugin calls host http stream read with an empty/zero StreamID, typically because it ignored an error from the open/execute step and used the zero-value field, or lost the ID variable.
Common situations: Error handling that continues after a failed stream open; copying example code that stubs the ID; schema drift where the response field name changed.
Related errors
- count must be a positive integer
- Codex live multipart boundary is missing
- Codex live call request requires an SDP offer
- invalid_realtime_client_secret
- invalid_expires_after
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/7afc8efb4848489c.
Report an issue: GitHub.