OpenAI requests sent without Authorization header — wizard-saved openai:default (type: api_key) profile is ignored at request time
Summary
After completing the QuickStart Docker onboarding with OpenAI as the model provider, every OpenAI Responses API call from the embedded agent fails with HTTP 401 — and the OpenAI error message confirms no Authorization header was sent at all ("Missing bearer or basic authentication in header"). The key itself is valid (verified independently with curl). The wizard correctly persists the key to ~/.openclaw/agents/main/agent/auth-profiles.json, but the embedded agent's HTTP client never includes it on requests to https://api.openai.com/v1/responses.
This makes a fresh QuickStart Docker install of OpenClaw with OpenAI completely unusable end-to-end — the agent boots, channels connect, but every model call 401s.
Environment
- OpenClaw built from
main at commit 7ebcce6a3d68d0e24b147c995c008d836466fadb ("test: clarify qmd manager assertions"), banner version 2026.5.6
- macOS 14 (Darwin 25.4.0), Apple Silicon
- Docker Desktop, Compose v2
- Install path:
git clone https://github.com/openclaw/openclaw.git && ./scripts/docker/setup.sh (the documented Containerized gateway flow)
Reproduced twice on two clean clones, with ~/.openclaw and the openclaw:local image fully wiped between runs.
Reproduction
-
Wipe any prior state: rm -rf ~/.openclaw && docker rmi openclaw:local.
-
git clone https://github.com/openclaw/openclaw.git && cd openclaw && ./scripts/docker/setup.sh.
-
Wizard: agree to the security disclaimer → QuickStart → OpenAI → paste a known-valid sk-proj-... key → keep openai/gpt-5.5 → Telegram (Bot API) + bot token → skip web search → Configure skills now? Yes → Skip dependencies, answer No to all skill API-key prompts (Google Places, Notion, Whisper, ElevenLabs) → Skip hooks → Hatch in Terminal.
-
The TUI boots. First model request fails immediately:
auth or provider access failed for openai. Run /auth openai to refresh credentials...
Evidence
1. Key was saved correctly
~/.openclaw/agents/main/agent/auth-profiles.json:
{
"version": 1,
"profiles": {
"openai:default": {
"type": "api_key",
"provider": "openai",
"key": "sk-proj-...REDACTED-164-chars..."
}
}
}
The key is 164 ASCII characters, no … masking artifact, no Unicode (verified key.isascii() == True).
~/.openclaw/openclaw.json carries only the shape of the profile, not the key:
"auth": {
"profiles": {
"openai:default": { "provider": "openai", "mode": "api_key" }
}
}
2. Key is valid against OpenAI
Independent verification with curl:
$ curl -sS -o /dev/null -w "HTTP %{http_code}\n" https://api.openai.com/v1/models \
-H "Authorization: Bearer sk-proj-..."
HTTP 200
3. OpenClaw sends no Authorization header
Gateway log entry from /tmp/openclaw/openclaw-2026-05-08.log inside the container (formatted; key fields preserved):
{
"subsystem": "model-fallback/decision",
"event": "model_fallback_decision",
"decision": "candidate_failed",
"requestedProvider": "openai",
"requestedModel": "gpt-5.5",
"candidateProvider": "openai",
"candidateModel": "gpt-5.5",
"reason": "auth",
"status": 401,
"errorPreview": "unexpected status 401 Unauthorized: Missing bearer or basic authentication in header, url: https://api.openai.com/v1/responses, cf-ray: 9f87f891ab72ecbc-WAW"
}
And from agent/embedded:
{
"event": "embedded_run_failover_decision",
"stage": "prompt",
"decision": "surface_error",
"failoverReason": "auth",
"profileFailureReason": "auth",
"provider": "openai",
"model": "gpt-5.5",
"fallbackConfigured": false,
"rawErrorPreview": "unexpected status 401 Unauthorized: Missing bearer or basic authentication in header"
}
OpenAI's "Missing bearer or basic authentication in header" specifically means the request arrived without an Authorization header at all — not with a wrong/expired token. So the OpenAI provider runtime is dispatching the HTTP request without ever attaching the saved profile's key.
4. Workarounds attempted, all failed
All run after the initial 401 was observed:
openclaw models auth add — adds a second profile openai:manual of type: token with the same valid key. Saved to auth-profiles.json correctly. Still 401, still "Missing bearer".
openclaw models auth order set --provider openai openai:manual — order override applied (visible in ~/.openclaw/agents/main/agent/auth-state.json). Still 401.
docker compose restart openclaw-gateway — gateway reloads cleanly (log: config change requires gateway restart (auth.profiles.openai:manual, auth.order) then "ready"). Still 401.
5. Suspected code area
src/agents/model-auth-runtime-shared.ts defines:
export function requireApiKey(auth: ResolvedProviderAuth, provider: string): string {
const key = normalizeSecretInput(auth.apiKey);
if (key) return key;
throw new Error(`No API key resolved for provider "${provider}"...`);
}
This requireApiKey should throw if it ever sees an empty key — but instead the request reaches OpenAI without a header, suggesting the OpenAI provider runtime is either bypassing requireApiKey entirely, or constructing the request with Authorization: Bearer ${undefined} (which fetch may strip), or reading the auth profile from a stale config snapshot that has only { provider, mode } from openclaw.json but not the key from auth-profiles.json.
The mtime of auth-profiles.json is included in the models-config fingerprint (src/agents/models-config.ts:59) so the file is known to the system — but its contents may not be threaded through to the actual provider HTTP client at request time.
Expected behavior
Embedded agent's first OpenAI Responses API call uses the openai:default profile's key from auth-profiles.json and succeeds (or fails with a real OpenAI auth error if the key is bad). No more "Missing bearer" 401s when a valid api_key profile exists.
Possible related work
The Unreleased changelog has:
Auth profiles: normalize inline API keys and tokens loaded from auth-profiles.json so masked or rich-text credential artifacts fail as auth errors instead of crashing HTTP header construction. Fixes #77624.
That fix targets masked keys (containing …). My key is clean ASCII, so #77624 is adjacent but not identical. There may be a deeper "where do we read the inline key from" gap that #77624's normalization does not cover.
What I can provide on request
- Full unredacted
~/.openclaw/openclaw.json (auth section only — already shown above).
- Full structured log lines (JSON) from
/tmp/openclaw/openclaw-2026-05-08.log for the failing run.
- A
tcpdump/mitmproxy trace of the outbound OpenAI request showing the exact headers sent (have not run yet, can if needed).
Happy to test patches against this exact reproduction.
OpenAI requests sent without
Authorizationheader — wizard-savedopenai:default(type: api_key) profile is ignored at request timeSummary
After completing the QuickStart Docker onboarding with OpenAI as the model provider, every OpenAI Responses API call from the embedded agent fails with HTTP 401 — and the OpenAI error message confirms no
Authorizationheader was sent at all ("Missing bearer or basic authentication in header"). The key itself is valid (verified independently withcurl). The wizard correctly persists the key to~/.openclaw/agents/main/agent/auth-profiles.json, but the embedded agent's HTTP client never includes it on requests tohttps://api.openai.com/v1/responses.This makes a fresh QuickStart Docker install of OpenClaw with OpenAI completely unusable end-to-end — the agent boots, channels connect, but every model call 401s.
Environment
mainat commit7ebcce6a3d68d0e24b147c995c008d836466fadb("test: clarify qmd manager assertions"), banner version2026.5.6git clone https://github.com/openclaw/openclaw.git && ./scripts/docker/setup.sh(the documented Containerized gateway flow)Reproduced twice on two clean clones, with
~/.openclawand theopenclaw:localimage fully wiped between runs.Reproduction
Wipe any prior state:
rm -rf ~/.openclaw && docker rmi openclaw:local.git clone https://github.com/openclaw/openclaw.git && cd openclaw && ./scripts/docker/setup.sh.Wizard: agree to the security disclaimer → QuickStart → OpenAI → paste a known-valid
sk-proj-...key → keepopenai/gpt-5.5→ Telegram (Bot API) + bot token → skip web search → Configure skills now? Yes → Skip dependencies, answer No to all skill API-key prompts (Google Places, Notion, Whisper, ElevenLabs) → Skip hooks → Hatch in Terminal.The TUI boots. First model request fails immediately:
Evidence
1. Key was saved correctly
~/.openclaw/agents/main/agent/auth-profiles.json:{ "version": 1, "profiles": { "openai:default": { "type": "api_key", "provider": "openai", "key": "sk-proj-...REDACTED-164-chars..." } } }The key is 164 ASCII characters, no
…masking artifact, no Unicode (verifiedkey.isascii() == True).~/.openclaw/openclaw.jsoncarries only the shape of the profile, not the key:2. Key is valid against OpenAI
Independent verification with
curl:3. OpenClaw sends no
AuthorizationheaderGateway log entry from
/tmp/openclaw/openclaw-2026-05-08.loginside the container (formatted; key fields preserved):{ "subsystem": "model-fallback/decision", "event": "model_fallback_decision", "decision": "candidate_failed", "requestedProvider": "openai", "requestedModel": "gpt-5.5", "candidateProvider": "openai", "candidateModel": "gpt-5.5", "reason": "auth", "status": 401, "errorPreview": "unexpected status 401 Unauthorized: Missing bearer or basic authentication in header, url: https://api.openai.com/v1/responses, cf-ray: 9f87f891ab72ecbc-WAW" }And from
agent/embedded:{ "event": "embedded_run_failover_decision", "stage": "prompt", "decision": "surface_error", "failoverReason": "auth", "profileFailureReason": "auth", "provider": "openai", "model": "gpt-5.5", "fallbackConfigured": false, "rawErrorPreview": "unexpected status 401 Unauthorized: Missing bearer or basic authentication in header" }OpenAI's "Missing bearer or basic authentication in header" specifically means the request arrived without an
Authorizationheader at all — not with a wrong/expired token. So the OpenAI provider runtime is dispatching the HTTP request without ever attaching the saved profile's key.4. Workarounds attempted, all failed
All run after the initial 401 was observed:
openclaw models auth add— adds a second profileopenai:manualoftype: tokenwith the same valid key. Saved toauth-profiles.jsoncorrectly. Still 401, still "Missing bearer".openclaw models auth order set --provider openai openai:manual— order override applied (visible in~/.openclaw/agents/main/agent/auth-state.json). Still 401.docker compose restart openclaw-gateway— gateway reloads cleanly (log:config change requires gateway restart (auth.profiles.openai:manual, auth.order)then "ready"). Still 401.5. Suspected code area
src/agents/model-auth-runtime-shared.tsdefines:This
requireApiKeyshould throw if it ever sees an empty key — but instead the request reaches OpenAI without a header, suggesting the OpenAI provider runtime is either bypassingrequireApiKeyentirely, or constructing the request withAuthorization: Bearer ${undefined}(whichfetchmay strip), or reading the auth profile from a stale config snapshot that has only{ provider, mode }fromopenclaw.jsonbut not thekeyfromauth-profiles.json.The mtime of
auth-profiles.jsonis included in the models-config fingerprint (src/agents/models-config.ts:59) so the file is known to the system — but its contents may not be threaded through to the actual provider HTTP client at request time.Expected behavior
Embedded agent's first OpenAI Responses API call uses the
openai:defaultprofile'skeyfromauth-profiles.jsonand succeeds (or fails with a real OpenAI auth error if the key is bad). No more "Missing bearer" 401s when a validapi_keyprofile exists.Possible related work
The
Unreleasedchangelog has:That fix targets masked keys (containing
…). My key is clean ASCII, so #77624 is adjacent but not identical. There may be a deeper "where do we read the inline key from" gap that #77624's normalization does not cover.What I can provide on request
~/.openclaw/openclaw.json(auth section only — already shown above)./tmp/openclaw/openclaw-2026-05-08.logfor the failing run.tcpdump/mitmproxytrace of the outbound OpenAI request showing the exact headers sent (have not run yet, can if needed).Happy to test patches against this exact reproduction.