{"record":{"id":"4e1a9d0a187137a7","repo":"router-for-me/CLIProxyAPI","slug":"invalid-session","errorCode":"invalid_session","errorMessage":"session must be a valid JSON object","messagePattern":"session must be a valid JSON object","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"internal/client/codex/live/client_secret.go","lineNumber":330,"sourceCode":"\tif expiresAfter.Anchor != \"\" && expiresAfter.Anchor != \"created_at\" {\n\t\treturn 0, errors.New(\"expires_after.anchor must be created_at\")\n\t}\n\tminimumSeconds := int64(clientSecretMinimumLifetime / time.Second)\n\tmaximumSeconds := int64(clientSecretMaximumLifetime / time.Second)\n\tif expiresAfter.Seconds < minimumSeconds || expiresAfter.Seconds > maximumSeconds {\n\t\treturn 0, fmt.Errorf(\"expires_after.seconds must be between %d and %d\", minimumSeconds, maximumSeconds)\n\t}\n\treturn time.Duration(expiresAfter.Seconds) * time.Second, nil\n}\n\nfunc normalizeClientSecretSession(session json.RawMessage) (json.RawMessage, json.RawMessage, error) {\n\ttrimmedSession := strings.TrimSpace(string(session))\n\tif trimmedSession == \"\" || trimmedSession == \"null\" {\n\t\tsession = json.RawMessage(`{\"type\":\"realtime\",\"model\":\"gpt-realtime\"}`)\n\t}\n\tvar clientSession map[string]any\n\tif errUnmarshal := json.Unmarshal(session, &clientSession); errUnmarshal != nil || clientSession == nil {\n\t\treturn nil, nil, errors.New(\"session must be a valid JSON object\")\n\t}\n\tsessionType, _ := clientSession[\"type\"].(string)\n\tif strings.TrimSpace(sessionType) == \"\" {\n\t\tsessionType = \"realtime\"\n\t\tclientSession[\"type\"] = sessionType\n\t}\n\tif sessionType != \"realtime\" {\n\t\treturn nil, nil, fmt.Errorf(\"%w by the Codex OAuth upstream: %q\", errUnsupportedSessionType, sessionType)\n\t}\n\tmodel, _ := clientSession[\"model\"].(string)\n\tif strings.TrimSpace(model) == \"\" {\n\t\tmodel = \"gpt-realtime\"\n\t\tclientSession[\"model\"] = model\n\t}\n\tclientEncoded, errMarshal := json.Marshal(clientSession)\n\tif errMarshal != nil {\n\t\treturn nil, nil, fmt.Errorf(\"encode Realtime session: %w\", errMarshal)\n\t}","sourceCodeStart":312,"sourceCodeEnd":348,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/client/codex/live/client_secret.go#L312-L348","documentation":"Thrown when the payload for host.model.stream_close fails to unmarshal into pluginapi.HostModelStreamCloseRequest (field: StreamID string). Wraps the encoding/json error with %w.","triggerScenarios":"Plugin sends malformed JSON to host.model.stream_close: wrong StreamID type, truncated body, or garbage bytes from the C buffer boundary.","commonSituations":"Plugin SDK version mismatch; serialization bug; reusing a buffer after free in a non-Go plugin.","solutions":["Send json.Marshal(pluginapi.HostModelStreamCloseRequest{StreamID: id}) output unchanged.","Rebuild the plugin against the host's pluginapi version.","Inspect the raw bytes at the RPC boundary if the cause is unclear."],"exampleFix":"// after\nraw, _ := json.Marshal(pluginapi.HostModelStreamCloseRequest{StreamID: streamID})\nhost.Call(ctx, \"host.model.stream_close\", raw)","handlingStrategy":"try-catch","validationCode":"raw, err := json.Marshal(pluginapi.HostModelStreamCloseRequest{StreamID: id})\nif err != nil {\n    return err\n}","typeGuard":null,"tryCatchPattern":"if _, err := host.Call(ctx, \"host.model.stream_close\", raw); err != nil {\n    log.Warnf(\"stream close failed for %s: %v\", id, err) // close is best-effort; log and continue\n}","preventionTips":["Always close streams you opened (defer the close call).","Marshal the typed struct.","Treat close failures as warnings — the bridge also cleans up via callback cleanup."],"tags":["pluginhost","json","rpc","deserialization"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}