router-for-me/CLIProxyAPI · error

Realtime client secrets require an SDP or JSON call request

Error message

Realtime client secrets require an SDP or JSON call request

What it means

Defensive guard in hostHTTPClient.doHTTP: the method is invoked on a nil client or a client whose host pointer is nil. In practice this means the HTTP bridge object was used after being torn down or was constructed outside the host.

Solutions

  1. Obtain the HTTP client from the live host API during a callback, not from a cached global.
  2. Do not issue host HTTP calls after host shutdown has begun.
  3. In tests, construct the bridge through the Host, not as a zero value.
Defensive patterns

Strategy: type-guard

Type guard

// Go: guard before use
if c == nil || c.host == nil {
    return errors.New("host http client is unavailable")
}

Try / catch

resp, err := client.Do(ctx, req)
if err != nil && strings.Contains(err.Error(), "host http client is unavailable") {
    // lifecycle bug: client used outside the host's lifetime — re-acquire from the live host API
}

Prevention

When it happens

Trigger: Calling Do/DoStream on a zero-value hostHTTPClient, or retaining the bridge after the Host was shut down and its back-references dropped.

Common situations: Embedders stashing the plugin-side HTTP client beyond the plugin/host lifecycle; races during shutdown; test code constructing the bridge directly.

Related errors


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

Appendix: source

Thrown at internal/client/codex/live/live.go:561

		contentType = "application/json"
	}
	return body, contentType, model, nil
}

func applyClientSecretCallSession(body []byte, contentType, model string, session json.RawMessage) ([]byte, string, string, error) {
	if len(session) == 0 {
		return body, contentType, model, nil
	}
	mediaType, _, errMediaType := mime.ParseMediaType(contentType)
	if errMediaType == nil && (strings.EqualFold(mediaType, "application/sdp") || strings.EqualFold(mediaType, "text/plain")) {
		encoded, errEncode := encodeCallRequest(string(body), session)
		if errEncode != nil {
			return nil, "", "", errEncode
		}
		return encoded, "application/json", modelFromJSON(session), nil
	}
	if errMediaType != nil || !strings.EqualFold(mediaType, "application/json") {
		return nil, "", "", errors.New("Realtime client secrets require an SDP or JSON call request")
	}
	var payload map[string]json.RawMessage
	if errUnmarshal := json.Unmarshal(body, &payload); errUnmarshal != nil {
		return nil, "", "", fmt.Errorf("failed to decode Realtime call request: %w", errUnmarshal)
	}
	payload["session"] = append(json.RawMessage(nil), session...)
	encoded, errMarshal := json.Marshal(payload)
	if errMarshal != nil {
		return nil, "", "", fmt.Errorf("failed to encode Realtime call request: %w", errMarshal)
	}
	return encoded, "application/json", modelFromJSON(session), nil
}

func rewriteCallRequestModel(body []byte, contentType, model string) ([]byte, string, error) {
	upstreamModel := codexRealtimeModel(model)
	mediaType, _, errMediaType := mime.ParseMediaType(contentType)
	if errMediaType != nil || !strings.EqualFold(mediaType, "application/json") || len(bytes.TrimSpace(body)) == 0 {
		return body, upstreamModel, nil

View on GitHub (pinned to 78f0c4079e)