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

  1. Stop reading once a read returns Done=true or an error; treat unknown-stream as terminal, not retryable.
  2. Close over the read loop so only one goroutine consumes a given StreamID.
  3. 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

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


AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15). Data as JSON: /api/errors/f1343af49aae43cc. Report an issue: GitHub.