{"record":{"id":"bf32e8eab8501aa3","repo":"router-for-me/CLIProxyAPI","slug":"invalid-expires-after","errorCode":"invalid_expires_after","errorMessage":"expires_after.anchor must be created_at","messagePattern":"expires_after\\.anchor must be created_at","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"internal/client/codex/live/client_secret.go","lineNumber":313,"sourceCode":"\t\tc.JSON(http.StatusOK, response)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, clientSecretCreateResponse{\n\t\tValue:     token,\n\t\tExpiresAt: expiresAt.Unix(),\n\t\tSession:   responseSession,\n\t})\n}\n\nfunc clientSecretLifetime(expiresAfter *struct {\n\tAnchor  string `json:\"anchor\"`\n\tSeconds int64  `json:\"seconds\"`\n}) (time.Duration, error) {\n\tif expiresAfter == nil {\n\t\treturn clientSecretDefaultLifetime, nil\n\t}\n\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}","sourceCodeStart":295,"sourceCodeEnd":331,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/client/codex/live/client_secret.go#L295-L331","documentation":"The stream-read callback rejects the call when the receiver Host is nil or its modelStreams bridge is nil. This is a host-side wiring/lifecycle error, not a bad stream ID (a bad ID instead yields 'not open' from the bridge).","triggerScenarios":"Calling host.model.stream_read on a Host that was never given a model stream bridge, or invoking the RPC method on a zero-value/partially constructed Host.","commonSituations":"Test harnesses building a Host by hand; embedding code that skips bridge initialization; calling into a Host that is mid-shutdown.","solutions":["Initialize the Host via the SDK builder so the model stream bridge exists.","Verify a successful host.model.execute_stream response (non-empty StreamID) before attempting stream_read.","Treat this error as a programming/setup bug: do not retry, fix host construction."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"if streamID == \"\" {\n    return errors.New(\"no open stream; cannot read\")\n}","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"stream bridge is unavailable\") {\n    // host-side wiring failure; not retryable from the plugin\n    return fmt.Errorf(\"host stream bridge missing: %w\", err)\n}","preventionTips":["Only issue stream_read after a successful execute_stream returned a StreamID.","Build the Host with the full SDK builder.","Treat bridge-unavailable as a setup bug, not a runtime condition."],"tags":["pluginhost","streaming","initialization"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}