Summary
OpenAI Realtime / Talk browser-session auth appears to read process.env.OPENAI_API_KEY directly and can pass an unresolved SecretRef string to OpenAI instead of resolving it first.
In a macOS Keychain-backed setup where the gateway LaunchAgent environment contains:
OPENAI_API_KEY=keychain:openclaw:OPENAI_API_KEY
Realtime browser session creation fails with OpenAI receiving the literal/placeholder value rather than the resolved sk-... key.
Observed behavior
talk.realtime.session fails with:
OpenAI Realtime browser session failed (401): Incorrect API key provided: keychain********************_KEY
[type=invalid_request_error, code=invalid_api_key]
The same Keychain entry resolves successfully via:
security find-generic-password -s openclaw -a OPENAI_API_KEY -w
and direct OpenAI API probes with the resolved value succeed, so this is not an invalid OpenAI key.
Likely cause
In the installed 2026.4.24 runtime, the OpenAI Realtime provider path uses roughly:
const apiKey = config.apiKey || process.env.OPENAI_API_KEY;
before POSTing to:
https://api.openai.com/v1/realtime/client_secrets
normalizeProviderConfig() correctly uses normalizeResolvedSecretInputString() for provider config apiKey, but the process.env.OPENAI_API_KEY fallback is not SecretRef-aware. If OpenClaw-managed launch env intentionally stores keychain refs rather than plaintext values, this fallback path passes the unresolved ref to OpenAI.
The same pattern also appears in the realtime voice bridge creation path.
Expected behavior
OpenAI Realtime should resolve supported SecretRef/env/keychain references before using the API key, or fail locally with a clear unresolved-secret error instead of sending the placeholder to OpenAI.
Ideally:
plugins.entries.voice-call.config.realtime.providers.openai.apiKey SecretRefs are resolved before use.
process.env.OPENAI_API_KEY fallback treats known OpenClaw SecretRef forms, such as keychain:openclaw:OPENAI_API_KEY, as references to resolve rather than literal keys.
- The resolved key should be cached in-process for the gateway lifetime/session rather than calling Keychain on every Realtime request.
- Plaintext secrets should not be written to
openclaw.json or LaunchAgent plists as the workaround.
Environment
- OpenClaw: 2026.4.24
- macOS LaunchAgent-managed gateway
- Node: 25.9.0
- OpenAI Realtime browser session / Talk path
- Secrets stored in macOS Keychain and exposed to OpenClaw launch env as
keychain:openclaw:<NAME> refs
Local workaround tested
A local hot patch resolved keychain:<service>:<account> via /usr/bin/security find-generic-password ... -w, cached the result in-process, and used that resolved value in both:
- Realtime browser session creation
- Realtime voice bridge creation
That eliminated the obvious broken code path without writing plaintext secrets to disk.
Summary
OpenAI Realtime / Talk browser-session auth appears to read
process.env.OPENAI_API_KEYdirectly and can pass an unresolved SecretRef string to OpenAI instead of resolving it first.In a macOS Keychain-backed setup where the gateway LaunchAgent environment contains:
Realtime browser session creation fails with OpenAI receiving the literal/placeholder value rather than the resolved
sk-...key.Observed behavior
talk.realtime.sessionfails with:The same Keychain entry resolves successfully via:
and direct OpenAI API probes with the resolved value succeed, so this is not an invalid OpenAI key.
Likely cause
In the installed 2026.4.24 runtime, the OpenAI Realtime provider path uses roughly:
before POSTing to:
normalizeProviderConfig()correctly usesnormalizeResolvedSecretInputString()for provider configapiKey, but theprocess.env.OPENAI_API_KEYfallback is not SecretRef-aware. If OpenClaw-managed launch env intentionally stores keychain refs rather than plaintext values, this fallback path passes the unresolved ref to OpenAI.The same pattern also appears in the realtime voice bridge creation path.
Expected behavior
OpenAI Realtime should resolve supported SecretRef/env/keychain references before using the API key, or fail locally with a clear unresolved-secret error instead of sending the placeholder to OpenAI.
Ideally:
plugins.entries.voice-call.config.realtime.providers.openai.apiKeySecretRefs are resolved before use.process.env.OPENAI_API_KEYfallback treats known OpenClaw SecretRef forms, such askeychain:openclaw:OPENAI_API_KEY, as references to resolve rather than literal keys.openclaw.jsonor LaunchAgent plists as the workaround.Environment
keychain:openclaw:<NAME>refsLocal workaround tested
A local hot patch resolved
keychain:<service>:<account>via/usr/bin/security find-generic-password ... -w, cached the result in-process, and used that resolved value in both:That eliminated the obvious broken code path without writing plaintext secrets to disk.