#401 Missing Authentication header on OpenRouter — agent can't make any model calls

1 messages · Page 1 of 1 (latest)

thorn compass
#

Running OpenClaw v2026.4.14 (control panel UI) on WSL2 Ubuntu 24.04, Windows 11.

Getting persistent 401 Missing Authentication header on all OpenRouter model calls. Key is valid and active on openrouter.ai. .env has OPENROUTER_API_KEY=sk-or-... present. Config references it correctly as ${OPENROUTER_API_KEY}. Gateway restarts and comes up fine. Agent boots but cannot complete any model calls.

This started after the agent attempted to switch models from OpenAI to OpenRouter this morning. Gateway was stopped twice via SIGTERM during that process and on restart the 401s began.

Anything that could cause the env variable to not be picked up correctly after a config change + restart?

simple bluffBOT
rigid domeBOT
#

To help others find answers, you can mark your question as solved via Right click solution message -> Apps -> ✅ Mark Solution

simple bluffBOT
shadow sageBOT
#

Yeah, a few things can cause exactly this, and the biggest one is .env location.

For Gateway/Control UI runs, OpenClaw loads env in this order:

  1. process env
  2. .env in the current working directory
  3. ~/.openclaw/.env
  4. config env block
  5. optional env.shellEnv.enabled

So if your OPENROUTER_API_KEY is only in a project/local .env, the daemon may never see it after restart, because its cwd is often not that directory. On WSL + service-ish launches, ~/.openclaw/.env is the reliable place.

A second gotcha: stored provider auth in auth-profiles.json can shadow env by default. So a stale/bad OpenRouter auth profile can win over ${OPENROUTER_API_KEY}.

What I’d try first:

#
openclaw gateway status
openclaw models status --probe --probe-provider openrouter
openclaw secrets audit --check

And specifically:

  • move/add the key to ~/.openclaw/.env
  • restart the gateway
  • if you want shell vars imported too, enable:
{
  env: {
    shellEnv: { enabled: true }
thorn compass
#

Following up on the OpenRouter 401 issue. Here are the results of the three checks you suggested:

Setup: OpenClaw v2026.4.14 (control panel UI), WSL2 Ubuntu 24.04, Windows 11. Agent is Steve, running as systemd service.

Background: This morning Steve was asked to switch from OpenAI/Anthropic API to OpenRouter to reduce costs. During that process the gateway was stopped twice via SIGTERM and on restart Steve started throwing 401 Missing Authentication header on every model call.


1. openclaw gateway status
Runtime: running, RPC probe: ok. Gateway healthy.


2. openclaw models status --probe --probe-provider openrouter

Key findings:

  • Default: openrouter/openrouter/auto (from openrouter/auto)
  • Fallbacks: 0
  • openrouter effective=profiles:auth-profiles.json | profiles=1 (api_key=1) | env=sk-or-v1...present | source=env: OPENROUTER_API_KEY
  • Auth probe result: openrouter/openrouter/auto | openrouter:default (api_key) | ok · 30.7s

So the probe PASSES but Steve still gets 401s mid-session on every actual model call.


3. openclaw secrets audit --check

Key finding: OPENROUTER_API_KEY appears twice in ~/.openclaw/.env — could a duplicate entry cause the 401s Steve sees mid-session even though the probe passes?

Also noted: openrouter apiKey is stored plaintext in both openclaw.json and auth-profiles.json. models.json also has a codex apiKey entry.


Summary of the puzzle:

  • Key is valid and active on openrouter.ai
  • Probe passes ✓
  • Config references ${OPENROUTER_API_KEY} correctly ✓
  • OPENROUTER_API_KEY appears twice in .env ?
  • Steve cannot complete any model calls — 401 on every attempt ✗

What would cause the probe to pass but live model calls to fail with 401?

shadow sageBOT
#

Yep, this actually points to a more specific culprit, and I don’t think the duplicate .env line is the main one.

The important clue is:

  • probe passes
  • live calls 401
  • plaintext OpenRouter creds exist in auth-profiles.json and openclaw.json

That split can happen because the auth paths are not identical.

What I’m pretty confident is happening:

  1. Probe is succeeding off the env key

    • probes prefer live/env creds so stale stored creds don’t mask them
  2. Runtime model calls are probably using stored OpenRouter auth first

    • normal runtime resolution is profile-first by default
#
  • so a stale/bad openrouter:default in Steve’s auth store can still win and send bad/no auth on real calls

That would explain the exact puzzle: probe OK, real calls fail.

A few concrete notes:

  • Duplicate OPENROUTER_API_KEY in ~/.openclaw/.env
    Bad hygiene, worth cleaning up, but by itself it usually would not create “probe passes, runtime fails” in the same gateway process.

  • If you didn’t pass --agent Steve, your probe may also have been checking the default agent, not Steve’s auth store. That matters because auth is per-agent.

What I’d do next:

openclaw models status --probe --probe-provider openrouter --agent Steve
#

Then make OpenRouter auth single-source for Steve:

  • keep exactly one OPENROUTER_API_KEY in ~/.openclaw/.env
  • scrub/remove plaintext OpenRouter creds from:
    • ~/.openclaw/openclaw.json
    • ~/.openclaw/agents/Steve/agent/auth-profiles.json

If you want the safer guided path, use:

openclaw secrets configure --agent Steve
openclaw secrets audit --check

I would also start a fresh chat after cleanup (/new), just to avoid any session-sticky model/auth state.

So, short version: I’d stop blaming env loading now. The gateway can see a valid key. This looks much more like stored auth shadowing the env key, possibly in Steve’s agent scope specifically.

thorn compass
#

OpenRouter logs show successful calls this morning. Probe passes. Is it possible the 401s were transient during the restart sequence and Steve is actually working now? Should I just try a fresh session with /new?

shadow sageBOT
#

Yeah, at this point I think /new is the right next move.

If:

  • OpenRouter shows successful calls,
  • the current probe passes,
  • and gateway health is fine,

then a stale/bad session state is a very plausible explanation. Existing sessions can hang onto old context from before/during the restart, so testing with a fresh session is the cheapest clean check.

I’d do exactly this:

  1. send /new
  2. ask Steve something tiny, like “say hi”
  3. if that works, the 401s were likely transient/session-scoped
  4. if it still 401s immediately in the fresh session, then I’d go back to the auth-shadowing angle
#

One nuance: if Steve is a named agent, and you haven’t already, the best follow-up check is still:

openclaw models status --probe --probe-provider openrouter --agent Steve

So yes, I’d absolutely try /new now. It’s the lowest-friction way to tell whether the problem is already gone.

thorn compass
#

Tried /new, Steve shows a timestamp and model tag at 7:48 but the message bubble is completely empty. Still no response. /new is queuing behind a stuck run again. Probe passes, gateway healthy. What next?

shadow sageBOT
#

Yeah, this has stopped looking like an OpenRouter auth problem.

If you get:

  • model tag + timestamp
  • empty bubble
  • /new queued behind an active run

that sounds more like a stuck run / reply delivery issue than bad provider auth.

Next step I’d try, in this order:

  1. Send /stop as a standalone message
    /new won’t help much if an existing run is wedged.

  2. After it stops, send /new again

#
  1. Ask Steve something tiny, like hi

If it still hangs after /stop + /new, then the next useful thing is not more auth checking, it’s logs at the moment it wedges:

openclaw status --all
openclaw logs --follow

Then reproduce one tiny message and grab the lines around that timestamp.

What I’d look for in the log tail:

  • delivery_failed
  • queuedFinal
  • stuck session
  • reply dispatch errors
  • any provider/model error right after the run starts
thorn compass
#

Update on the 401 issue — it's gotten clearer but also stranger.

New developments:

  • /stop and /new had no effect — Steve kept producing empty response bubbles with model tag but no content
  • After every gateway restart, 401 returns immediately
  • The 401 is NOT just OpenRouter — Steve is also reporting it on Kimi via Groq, and the cron job 'Delayed Startup Announcement' is also failing with 401 Missing Authentication header
  • This suggests the 401 is happening across ALL model calls, not just OpenRouter
  • Steve is also posting the error directly to Discord automatically

Question 1: A couple of days ago there was a reported OpenRouter/provider glitch. Could this be related, or is a 401 across all providers pointing to something else entirely?

Question 2: The control panel UI 'Update now' button does nothing. Can the update to v2026.4.15 be done via WSL/terminal, and should that be done first before anything else?

Question 3: OpenAI Codex OAuth is valid and has usage (confirmed via probe: expires in 3d, 5h 100% left). Everything worked fine on Codex before. Can we switch Steve's default model to Codex via terminal as a stable fallback while we sort this properly? What's the safest command to do that?

Agent ID is 'main' (not 'Steve' — that's just the display name).

shadow sageBOT
#

This is a lot clearer now.

Q1: No, this does not smell like the OpenRouter-only glitch anymore.
If you’re seeing the same 401 Missing Authentication header across OpenRouter, Kimi/Groq, and even a cron, that points to something shared on the local OpenClaw side: auth/runtime resolution, a common header/base-url override, or a version bug. Not a single provider outage.

Q2: Yes, you can update from WSL terminal.
Current docs say the supported updater is:

openclaw update

That should detect install type, update, run doctor, and restart the gateway.

If you want to verify after:

#
openclaw --version
openclaw health

Q3: Yes, you can switch main back to Codex safely from terminal.
The exact provider docs command is:

openclaw config set agents.defaults.model.primary openai-codex/gpt-5.4
openclaw gateway restart
openclaw models status --probe --probe-provider openai-codex --agent main

My recommendation

Because you already know Codex auth is healthy, I’d do this order:

thorn compass
#

Ran all three steps in the order you suggested:

  1. openclaw config set agents.defaults.model.primary openai-codex/gpt-5.4 — ran successfully, output said "Updated agents.defaults.model.primary. Restart the gateway to apply."
  2. openclaw gateway restart — restarted successfully
  3. openclaw models status --probe --probe-provider openai-codex --agent main — Codex probe passes (ok · 37.5s, OAuth valid, expires in 3d)

The problem:
Despite the config set succeeding and gateway restarting, models status still shows:

Default (agent): openrouter/moonshotai/kimi-k2.5

Not openai-codex/gpt-5.4 as expected. The change does not appear to have taken effect.

Steve is also still throwing 401 Missing Authentication header on every model call in the control panel.

Why would config set report success but the agent default not change after a gateway restart? Is there a per-agent config that's overriding the defaults level setting?

shadow sageBOT
#

Yeah, that strongly suggests a per-agent override.

agents.defaults.model.primary is only the global default.
If main has its own agents.list[].model, that wins over agents.defaults.*.

That exactly matches what you’re seeing:

  • config set agents.defaults.model.primary ... succeeds
  • restart succeeds
  • models status --agent main still shows openrouter/moonshotai/kimi-k2.5

So the next check is:

openclaw config get agents.defaults.model --json
openclaw config get agents.list --json
#

Look for the entry with:

"id": "main"

If that entry has a model field, that is the override you need to change.

Then set the main agent model, not the global default

If main is the first entry and model is a simple string, it would be:

openclaw config set agents.list[0].model openai-codex/gpt-5.4
openclaw gateway restart
openclaw models status --agent main
#

If the model field is object form, set .primary instead:

openclaw config set agents.list[0].model.primary openai-codex/gpt-5.4
openclaw gateway restart
openclaw models status --agent main

So yes, I think you found the reason: main is probably overriding agents.defaults.

Docs:

If you paste the openclaw config get agents.list --json output, I can tell you the exact path to set.

thorn compass
#

mon@FURBIFY-MGI9IH8:/mnt/c/windows/system32$ openclaw config get agents.list --json
[
{
"id": "main",
"model": "openrouter/moonshotai/kimi-k2.5"
}
]
mon@FURBIFY-MGI9IH8:/mnt/c/windows/system32$

shadow sageBOT
#

Yep, that’s the smoking gun.

main has an explicit per-agent override:

{
  "id": "main",
  "model": "openrouter/moonshotai/kimi-k2.5"
}

So agents.defaults.model.primary was never going to win.

You’ve got two safe fixes:

Option A, most explicit

Set main directly to Codex:

#
openclaw config set agents.list[0].model openai-codex/gpt-5.4
openclaw gateway restart
openclaw models status --probe --probe-provider openai-codex --agent main

Option B, cleaner inheritance

Remove the override and let main inherit the default you already set:

openclaw config unset agents.list[0].model
openclaw gateway restart
openclaw models status --agent main

I’d use Option A right now, because it’s the most explicit and least ambiguous while you’re debugging.

thorn compass
#

Thank you, Steve is back on Codex and replying now

shadow sageBOT
#

Nice, glad Steve’s back 🎉

At this point I’d keep Codex as the stable fallback, then when you have a calm minute:

openclaw update

and only retest OpenRouter after that.

If this fixed it, please right-click the solution message → Apps → ✅ Mark Solution so Answer Overflow can index it for the next person.