openclaw - ✅(Solved) Fix OpenAI Realtime browser session sends unresolved keychain SecretRef as API key [1 pull requests, 2 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#72120Fetched 2026-04-27 05:34:37
View on GitHub
Comments
2
Participants
2
Timeline
5
Reactions
0
Author
Timeline (top)
commented ×2cross-referenced ×2closed ×1

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.

Error Message

talk.realtime.session fails with:

Root Cause

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.

Fix Action

Fix / Workaround

  • 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.

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:

PR fix notes

PR #72126: fix(openai): resolve keychain refs for realtime API keys

Description (problem / solution / changelog)

Summary

  • Resolve keychain:<service>:<account> OpenAI Realtime API-key refs before using them
  • Cache resolved Keychain refs in-process so Realtime does not shell out for every request
  • Use the resolver for both browser client-secret session creation and server-side voice bridge creation

Fixes #72120.

Verification

  • pnpm install --frozen-lockfile
  • pnpm exec vitest run extensions/openai/realtime-voice-provider.test.ts --config test/vitest/vitest.extensions.config.ts
  • git diff --check

Also verified locally against a running gateway hot patch: talk.realtime.session succeeded after this resolver was applied while OPENAI_API_KEY still contained keychain:openclaw:OPENAI_API_KEY.

Notes

I tried pnpm exec tsc --noEmit -p tsconfig.extensions.json --pretty false, but the current tree reports an unrelated pre-existing type error:

src/plugins/synthetic-auth.runtime.ts(26,56): error TS2339: Property 'syntheticAuthRefs' does not exist on type 'InstalledPluginIndexRecord'.

Changed files

  • extensions/openai/realtime-voice-provider.ts (modified, +74/-10)

Code Example

OPENAI_API_KEY=keychain:openclaw:OPENAI_API_KEY

---

OpenAI Realtime browser session failed (401): Incorrect API key provided: keychain********************_KEY
[type=invalid_request_error, code=invalid_api_key]

---

security find-generic-password -s openclaw -a OPENAI_API_KEY -w

---

const apiKey = config.apiKey || process.env.OPENAI_API_KEY;

---

https://api.openai.com/v1/realtime/client_secrets
RAW_BUFFERClick to expand / collapse

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.

extent analysis

TL;DR

Modify the OpenAI Realtime provider to resolve SecretRef/env/keychain references before using the API key.

Guidance

  • Identify and update the process.env.OPENAI_API_KEY fallback path to treat known OpenClaw SecretRef forms as references to resolve.
  • Implement a caching mechanism to store the resolved key in-process for the gateway lifetime/session, rather than calling Keychain on every Realtime request.
  • Verify that plugins.entries.voice-call.config.realtime.providers.openai.apiKey SecretRefs are resolved before use.
  • Test the changes with a local hot patch, as described in the issue, to ensure the resolved value is used correctly in both Realtime browser session creation and Realtime voice bridge creation.

Example

const apiKey = config.apiKey || resolveSecretRef(process.env.OPENAI_API_KEY);
// Implement resolveSecretRef function to handle keychain:openclaw:<NAME> refs
function resolveSecretRef(ref) {
  if (ref.startsWith('keychain:')) {
    // Use /usr/bin/security find-generic-password to resolve the ref
    const resolvedKey = execSync(`/usr/bin/security find-generic-password -s ${ref.split(':')[1]} -a ${ref.split(':')[2]} -w`);
    return resolvedKey.toString().trim();
  }
  return ref;
}

Notes

The provided example is a simplified illustration and may require adjustments to fit the actual implementation. The resolveSecretRef function should be implemented to handle the specific SecretRef format used in the environment.

Recommendation

Apply a workaround by modifying the OpenAI Realtime provider to resolve SecretRef/env/keychain references, as this will allow for a more secure and reliable solution without writing plaintext secrets to disk.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

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.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix OpenAI Realtime browser session sends unresolved keychain SecretRef as API key [1 pull requests, 2 comments, 2 participants]