fix(server): read gateway bearer token at request time and honor HERMES_API_TOKEN#234
Merged
Merged
Conversation
…ES_API_TOKEN
src/server/openai-compat-api.ts has two issues that combine to break
the chat surface for any deployment whose Hermes Agent gateway has
`API_SERVER_KEY` set (i.e. anything that isn't an open loopback
gateway):
1. The local `BEARER_TOKEN` const reads only `CLAUDE_API_TOKEN`,
ignoring the documented `HERMES_API_TOKEN` env var that the README
tells users to set. Looks like a leftover from the Claude → Hermes
rename: the const in src/server/gateway-capabilities.ts:222 honors
both names, but this local one was never updated.
2. The const is evaluated at module-load time. Under vite-node SSR
(`pnpm dev`), the module can be loaded in a worker context where
`process.env` doesn't yet contain the values that systemd /
EnvironmentFile / .env populated for the parent node process.
That freezes the constant to '' permanently, even though the env
is correctly populated by the time `openaiChat` actually runs.
Symptom: chat UI loads, sessions/skills/memory all work, but every
message produces a run with `status: error` and:
errorMessage: "OpenAI-compatible chat: 401 {\"error\": {...,
\"code\": \"invalid_api_key\"}}"
…in `<HERMES_HOME>/webui-mvp/runs/<session>/<run>.json`. The error
format matches what the gateway returns for missing Authorization, not
an upstream provider error. Confirmed via instrumentation:
[DEBUG-AUTH] BEARER_TOKEN length: 0
env.HERMES_API_TOKEN length: 16
Fix: replace the const with a small `getBearerToken()` helper that
reads the env at call time and honors HERMES_API_TOKEN with a fallback
to CLAUDE_API_TOKEN. Three call sites updated (`getDefaultModel`,
`openaiChat` Authorization header, and the session-id guard).
No behavior change for setups that already worked (open loopback
gateway with no API_SERVER_KEY, or production builds where
process.env is fully populated before module load).
This was referenced May 3, 2026
outsourc-e
added a commit
that referenced
this pull request
May 3, 2026
…ack (#265) * fix(api): replace broken 'authResult as unknown as Response' cast with proper 401 isAuthenticated() returns boolean. The previous pattern: const authResult = isAuthenticated(request) if (authResult !== true) return authResult as unknown as Response silenced the TypeScript error but threw HTTPError -> 500 at runtime because the framework received `false` instead of a Response. This broke /api/connection-status entirely on protected setups (causing ONBOARDING_KEY to never persist on fresh installs) and would have broken the just-merged /api/system-metrics in the same way. Replace with the canonical pattern used by every other API route: if (!isAuthenticated(request)) { return json({ error: 'Unauthorized' }, { status: 401 }) } Refs #261 (which spotted the pattern in connection-status), #246 (which copied the broken pattern into system-metrics). * fix(claude-proxy): fall back to /v1/models for /api/available-models on vanilla agent Vanilla hermes-agent (any version through 2026-05) does not expose `/api/available-models` \u2014 that endpoint is legacy fork-only. The chat composer + settings dialog hit `/api/claude-proxy/api/available-models` expecting it to work, get 404, and fall through to broken UI states where the model picker is empty. Fix: when proxying GET /api/available-models and the upstream returns 404, synthesize a compatible `{ models: [...] }` response from /v1/models filtered by ?provider= so the picker keeps working. Also: read the bearer token at request time using the same precedence as the rest of the codebase (HERMES_API_TOKEN || CLAUDE_API_TOKEN || module-level BEARER_TOKEN). PR #234 fixed this in openai-compat-api.ts; this catches the proxy path that was missed. Refs #261. --------- Co-authored-by: Aurora release bot <release@outsourc-e.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
src/server/openai-compat-api.tshas two issues that combine to break the chat surface for any deployment whose Hermes Agent gateway hasAPI_SERVER_KEYset (i.e. anything that isn't an open loopback gateway):The local
BEARER_TOKENconst reads onlyCLAUDE_API_TOKEN, ignoring the documentedHERMES_API_TOKENenv var that the README ("attach the workspace to existing hermes-agent") and the .env.example tell users to set. Looks like a leftover from the Claude → Hermes rename: the analogous const insrc/server/gateway-capabilities.ts:222honors both names, but this local one was never updated.The const is evaluated at module-load time. Under vite-node SSR (
pnpm dev), the module can be loaded in a worker context whereprocess.envdoesn't yet contain the values that systemd / EnvironmentFile / .env populated for the parent node process. That freezes the constant to''permanently, even though the env is correctly populated by the timeopenaiChatactually runs.Symptom
Chat UI loads, sessions/skills/memory all work, but every message produces a run with
status: errorand:…in
<HERMES_HOME>/webui-mvp/runs/<session>/<run>.json. The error format matches what the gateway returns for missing Authorization, not an upstream provider error.Confirmed via instrumentation in the request handler:
Same
process.env, two different observed values — the const captured an empty snapshot.Repro
hermes-agentgateway withAPI_SERVER_KEY=<anything>(so the gateway requiresAuthorization: Bearer)HERMES_API_TOKEN=<same>in workspace.envpnpm devwebui-mvp/runs/<session>/Fix
Replace the const with a small
getBearerToken()helper that reads env at call time and honorsHERMES_API_TOKENwith fallback toCLAUDE_API_TOKEN. Three call sites updated (getDefaultModel,openaiChatAuthorization header, and the session-id guard).No behavior change for setups that already worked (open loopback gateway with no
API_SERVER_KEY, or production builds whereprocess.envis fully populated before module load).Test plan
pnpm exec tsc --noEmit— passes for the changed fileAPI_SERVER_KEYset on the gateway andHERMES_API_TOKENset on the workspace, chat works end-to-end viapnpm dev(was 401 before, 200 after)API_SERVER_KEY(loopback / portable), behavior unchanged