Skip to content

fix(agents): inject resolved OAuth bearer into boundary-aware embedded streams#73588

Merged
openperf merged 1 commit into
openclaw:mainfrom
openperf:fix/73559-codex-oauth-bearer
Apr 29, 2026
Merged

fix(agents): inject resolved OAuth bearer into boundary-aware embedded streams#73588
openperf merged 1 commit into
openclaw:mainfrom
openperf:fix/73559-codex-oauth-bearer

Conversation

@openperf

@openperf openperf commented Apr 28, 2026

Copy link
Copy Markdown
Member

Summary

  • Problem: Sending any UI message while the default model is openai-codex/gpt-5.5 causes the embedded run to fail before reply with unexpected status 401 Unauthorized: Missing bearer or basic authentication in header, url: https://api.openai.com/v1/responses. Logs show three matching errors at the lane (lane=main), session-agent (lane=session:agent:main:main) and embedded-agent layers. The resolved Codex OAuth profile is present and validated (openclaw models status reports it as healthy), but the OpenAI Responses HTTP request goes out with no bearer header. The defect is in src/agents/pi-embedded-runner/stream-resolution.ts:124 (pre-fix), inside resolveEmbeddedAgentStreamFn.
  • Root Cause: resolveEmbeddedAgentStreamFn injects the resolved runtime apiKey (the OAuth bearer that the embedded run layer already resolved at src/agents/pi-embedded-runner/run.ts:867 and forwarded as params.resolvedApiKey from src/agents/pi-embedded-runner/run/attempt.ts:1611) only on the provider-owned branch. On the boundary-aware fallback branch — taken whenever a model resolves to a transport-aware API like openai-codex-responses without a registered provider stream — the function simply return boundaryAwareStreamFn; and the resolved key is dropped. createOpenAIResponsesTransportStreamFn at src/agents/openai-transport-stream.ts:738 then constructs the OpenAI client from options?.apiKey || getEnvApiKey(model.provider) || "". For OAuth-only providers like openai-codex there is no env var, options.apiKey was never injected, so the SDK is created with apiKey: "" and OpenAI rejects the request with the 401 above. gpt-5.5 is the first widely-used Codex model that takes this exact boundary-aware:openai-codex-responses lane (the openai-codex plugin does not register a provider-owned stream for the Codex Responses API — see extensions/openai/openai-codex-provider.ts:175), which is why the regression surfaces specifically on the openai-codex/gpt-5.5 upgrade and stays present after re-running OpenAI Codex OAuth login.
  • Fix: Extract the existing resolvedApiKey + run-signal injection logic from the provider-owned branch into a small private helper wrapEmbeddedAgentStreamFn, and apply the same wrapper to the boundary-aware fallback. The provider-owned branch keeps its stripSystemPromptCacheBoundary context normalization (passed in as the optional transformContext callback). The boundary-aware branch deliberately omits that callback because boundary-aware transports (createOpenAIResponsesTransportStreamFn, createAnthropicMessagesTransportStreamFn, etc.) already strip the cache boundary internally — see src/agents/openai-transport-stream.ts:254, :870, :1755 — so re-stripping would be a behavior change. Auth precedence inside the helper is preserved exactly: resolvedApiKey?.trim() wins, then authStorage.getApiKey(provider), then any pre-existing options.apiKey. This is the minimal change that closes the credential gap on every boundary-aware transport (Codex Responses, OpenAI Responses, OpenAI Completions, Azure OpenAI Responses, Anthropic Messages, Google Generative AI) without touching provider plugins, transport internals, OAuth refresh, or the Responses URL.
  • What changed:
    • src/agents/pi-embedded-runner/stream-resolution.ts: refactored resolveEmbeddedAgentStreamFn to delegate to a new private wrapEmbeddedAgentStreamFn helper; the boundary-aware fallback now wraps the resolved transport with the same auth/signal injection that the provider-owned branch already had.
    • src/agents/pi-embedded-runner/stream-resolution.test.ts: added four focused regression tests covering resolved-key injection, authStorage fallback, run-signal forwarding, and cache-boundary preservation on the boundary-aware fallback path. Existing tests (provider-owned auth/signal, fallback shape labels) are unchanged.
  • What did NOT change (scope boundary):
    • No change to extensions/openai/openai-codex-provider.ts, the OpenAI Codex OAuth login flow, model registry, or auth-profiles.json storage.
    • No change to createOpenAIResponsesTransportStreamFn or any of the boundary-aware transports — the apiKey || getEnvApiKey(model.provider) || "" chain remains exactly as it was.
    • No change to provider-owned stream behavior, system prompt cache boundary stripping, WebSocket transport, Anthropic Vertex stream, or the run / attempt control flow.
    • No new public API, no new exports, no any, no widened types — wrapEmbeddedAgentStreamFn is a file-local helper.
    • No change to gateway protocol, channels, plugins SDK, settings schema, or docs/changelog beyond what the regression test asserts.

Reproduction

  1. Configure OpenAI Codex OAuth on main.
  2. openclaw models set openai-codex/gpt-5.5
  3. openclaw models status — confirms the OAuth profile resolves cleanly.
  4. openclaw gateway --port 18789
  5. Send any message in the Control UI.
  6. Observe no assistant reply.
  7. Tail the gateway log:
    grep -Ei 'embedded|lane task|401|unauthorized|responses' /tmp/openclaw/openclaw-2026-04-28.log | tail -n 40
    
    Expected (current main):
    lane task error: lane=main durationMs=87316 error="unexpected status 401 Unauthorized: Missing bearer or basic authentication in header, url: https://api.openai.com/v1/responses"
    lane task error: lane=session:agent:main:main durationMs=87319 error="unexpected status 401 Unauthorized: Missing bearer or basic authentication in header, url: https://api.openai.com/v1/responses"
    Embedded agent failed before reply: unexpected status 401 Unauthorized: Missing bearer or basic authentication in header, url: https://api.openai.com/v1/responses
    
  8. Apply this PR. Repeat steps 4–6 — the embedded run completes and the assistant reply renders normally.

The new unit test injects the resolved run api key into the boundary-aware Codex Responses fallback reproduces the credential drop deterministically without any network or live OAuth.

Risk / Mitigation

  • Risk: The wrapper is now applied to every boundary-aware transport (openai-responses, openai-codex-responses, openai-completions, azure-openai-responses, anthropic-messages, google-generative-ai). For non-OAuth providers that previously relied on getEnvApiKey(model.provider) inside the transport, an empty params.resolvedApiKey and missing params.authStorage would now still produce no options.apiKey — exactly as before. For env-keyed providers that do have an authStorage entry, authStorage.getApiKey(provider) returns the same key the transport would have read from env, so behavior is preserved. The auth precedence (resolvedApiKeyauthStorage → existing options.apiKey) is bit-identical to the provider-owned branch that has been in production since d12987d72.
  • Risk: The boundary-aware path now also forwards the run abort signal when callers do not pass one explicitly, mirroring the provider-owned branch added in d12987d72. Cancellation reaches the OpenAI SDK AbortSignal slot the same way; the existing test does not overwrite an explicit provider-owned stream signal proves explicit caller signals still win, and the new boundary-aware sibling test asserts the same precedence.
  • Mitigation: Four new regression tests in stream-resolution.test.ts lock the contract: resolved-key injection, authStorage fallback, run-signal forwarding, and cache-boundary preservation on the boundary-aware path. The cache-boundary test directly proves the fix did not silently start re-stripping <<openclaw-cache-boundary>> markers (which would corrupt prompt-cache hit rates on Codex Responses). Existing provider-owned coverage is untouched and continues to assert the prior behavior end-to-end.

Change Type (select all)

  • Bug fix

Scope (select all touched areas)

  • Gateway / orchestration
  • Auth / tokens
  • Integrations

Linked Issue/PR

Fixes #73559

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: S maintainer Maintainer-authored PR labels Apr 28, 2026
@greptile-apps

greptile-apps Bot commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a 401 Unauthorized error that occurs when using openai-codex/gpt-5.5 as the default model. The root cause was that resolveEmbeddedAgentStreamFn forwarded the resolved OAuth bearer token (params.resolvedApiKey) and the run abort signal only on the provider-owned branch, but returned the boundary-aware transport function (boundaryAwareStreamFn) unwrapped, leaving both unset. The fix extracts the auth/signal injection into a private wrapEmbeddedAgentStreamFn helper and applies it to the boundary-aware fallback path as well, with four new regression tests covering all injected fields.

Confidence Score: 5/5

Safe to merge — minimal, targeted bug fix with thorough test coverage and no interface changes.

The change is small and surgical: a new private helper consolidates duplicated injection logic, and both branches are exercised by both old and new tests. Auth precedence (resolvedApiKey → authStorage → existing options.apiKey) is preserved exactly. The boundary-aware fast path (no auth) now also forwards the run abort signal, which is a safe improvement. No public API, exports, types, or transport internals were changed.

No files require special attention.

Reviews (1): Last reviewed commit: "fix(agents): inject resolved OAuth beare..." | Re-trigger Greptile

@clawsweeper

clawsweeper Bot commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

Codex review: keeping this open for maintainer follow-up; there is still a little grit to resolve.

Keep open. This PR is member-authored and has the protected maintainer label, so cleanup policy requires explicit maintainer handling. It is also not obsolete on current main: the boundary-aware embedded stream fallback still returns the transport unwrapped, while resolved OAuth bearer/auth-storage and run-signal injection only happen on the provider-owned branch.

Best possible solution:

Keep this PR open for explicit maintainer review. The likely best path is to land a narrow src/agents/pi-embedded-runner/stream-resolution.ts fix that wraps boundary-aware embedded streams with the same resolved-key/auth-storage and run-signal handling as provider-owned streams, while preserving boundary-aware cache-boundary handling, plus focused regression tests for the linked Codex OAuth bearer regression #73559.

What I checked:

  • Protected maintainer handling: Provided GitHub context shows author association MEMBER and label maintainer; repository cleanup policy says these items stay open for explicit maintainer judgment.
  • Current main still returns boundary-aware stream unwrapped: resolveEmbeddedAgentStreamFn creates a boundary-aware stream for supported streamSimple fallback models, but returns boundaryAwareStreamFn directly, so it does not add apiKey or the run abort signal on that path. (src/agents/pi-embedded-runner/stream-resolution.ts:124, ab5c8025c9d0)
  • Provider-owned path has the missing wrapper behavior: The provider-owned branch resolves resolvedApiKey/authStorage, merges the run signal, and passes apiKey into the inner stream; the boundary-aware branch does not share that wrapper on current main. (src/agents/pi-embedded-runner/stream-resolution.ts:75, ab5c8025c9d0)
  • Resolved bearer exists before stream resolution: The run layer computes resolvedStreamApiKey from apiKeyInfo.apiKey when runtime auth exchange did not replace the credential, then passes it into the embedded attempt. (src/agents/pi-embedded-runner/run.ts:870, ab5c8025c9d0)
  • Attempt forwards auth inputs to stream resolution: The embedded attempt passes signal, resolvedApiKey, and authStorage into resolveEmbeddedAgentStreamFn; the remaining gap is inside stream resolution’s boundary-aware fallback branch. (src/agents/pi-embedded-runner/run/attempt.ts:1620, ab5c8025c9d0)
  • Responses transport depends on stream options for bearer auth: The OpenAI Responses transport builds its client from options?.apiKey || getEnvApiKey(model.provider) || "", so OAuth-only providers need the resolved bearer injected into stream options when no environment key exists. (src/agents/openai-transport-stream.ts:738, ab5c8025c9d0)

Likely related people:

  • Vincent Koc: Current-main blame attributes the affected stream-resolution helper, boundary-aware fallback branch, provider-transport registry, OpenAI Responses apiKey lookup, and Codex GPT-5.5 resolver in the available history to commit b96e7739a993ed42085d0d290ff1139ffac33f86. That makes this the strongest routing candidate from the local feature-history hunt. (role: current-main introducer / adjacent owner; confidence: medium; commits: b96e7739a993; files: src/agents/pi-embedded-runner/stream-resolution.ts, src/agents/provider-transport-stream.ts, src/agents/openai-transport-stream.ts)

Remaining risk / open question:

  • The PR intentionally changes bearer-token propagation for all boundary-aware transports that receive resolvedApiKey or authStorage, so maintainer review should confirm provider scoping and auth precedence remain intended.
  • Because this touches OAuth bearer handling, merge review should verify no token material is logged, persisted, exposed through test output, or forwarded to an unintended provider/runtime path.
  • No local tests were run because this was a read-only cleanup review; before merge, run the targeted stream-resolution tests and the appropriate changed gate.

Codex review notes: model gpt-5.5, reasoning high; reviewed against ab5c8025c9d0.

@openperf openperf force-pushed the fix/73559-codex-oauth-bearer branch from 73a3773 to 5367533 Compare April 28, 2026 13:55
@openperf openperf force-pushed the fix/73559-codex-oauth-bearer branch 3 times, most recently from 7f3121c to 323917b Compare April 29, 2026 03:16
@openperf openperf force-pushed the fix/73559-codex-oauth-bearer branch from 323917b to b3659c6 Compare April 29, 2026 04:55
@openperf openperf merged commit 16fd9a9 into openclaw:main Apr 29, 2026
60 of 62 checks passed
@openperf

Copy link
Copy Markdown
Member Author

Landed as 16fd9a9d59 on main.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
…d streams (openclaw#73588)

Fixes openclaw#73559. Extracts a shared wrapEmbeddedAgentStreamFn helper and applies it to both provider-owned and boundary-aware fallback paths in resolveEmbeddedAgentStreamFn, forwarding the resolved OAuth bearer (resolvedApiKey → authStorage → options.apiKey) and run abort signal so models routing through openai-codex-responses and other boundary-aware transports stop failing with 401 Missing bearer auth header.
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
…d streams (openclaw#73588)

Fixes openclaw#73559. Extracts a shared wrapEmbeddedAgentStreamFn helper and applies it to both provider-owned and boundary-aware fallback paths in resolveEmbeddedAgentStreamFn, forwarding the resolved OAuth bearer (resolvedApiKey → authStorage → options.apiKey) and run abort signal so models routing through openai-codex-responses and other boundary-aware transports stop failing with 401 Missing bearer auth header.
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
…d streams (openclaw#73588)

Fixes openclaw#73559. Extracts a shared wrapEmbeddedAgentStreamFn helper and applies it to both provider-owned and boundary-aware fallback paths in resolveEmbeddedAgentStreamFn, forwarding the resolved OAuth bearer (resolvedApiKey → authStorage → options.apiKey) and run abort signal so models routing through openai-codex-responses and other boundary-aware transports stop failing with 401 Missing bearer auth header.
sablehead pushed a commit to sablehead/openclaw that referenced this pull request Jun 10, 2026
…d streams (openclaw#73588)

Fixes openclaw#73559. Extracts a shared wrapEmbeddedAgentStreamFn helper and applies it to both provider-owned and boundary-aware fallback paths in resolveEmbeddedAgentStreamFn, forwarding the resolved OAuth bearer (resolvedApiKey → authStorage → options.apiKey) and run abort signal so models routing through openai-codex-responses and other boundary-aware transports stop failing with 401 Missing bearer auth header.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling maintainer Maintainer-authored PR size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GPT-5.5 OAuth requests fail with 401 Missing bearer auth header

1 participant