What happened?
Companion issue (interpreter side): "Feature request: verify LibreChat-minted JWTs (LibreChat v0.8.6+ no longer sends x-api-key)" — filed at usnavy13/LibreCodeInterpreter · usnavy13/LibreCodeInterpreter#121
The two issues are halves of one incompatibility: LibreChat stopped sending x-api-key, and interpreters implementing the documented contract have no way to accept what it sends instead. Either side closing the gap would resolve it.
What happened?
With a self-hosted code interpreter configured per the Code Interpreter docs — LIBRECHAT_CODE_BASEURL + LIBRECHAT_CODE_API_KEY — every execution fails, and reading the shipped code shows why: LIBRECHAT_CODE_API_KEY is never read when building the outbound request headers.
The docs describe the env var as the global setup path:
Global setup: use LIBRECHAT_CODE_API_KEY environment variable in the .env file of your project (provides access to all users)
…
Include the API key in your requests using the x-api-key header
But in v0.8.7, x-api-key is never attached.
Root cause (from the shipped bundle)
/app/packages/api/dist/index.cjs:
function isManagedCodeApiJwtMode() {
const provider = process.env.CODEAPI_AUTH_PROVIDER;
return provider === "librechat-jwt" || provider === "both";
}
function isCodeApiJwtAuthEnabled() {
return isManagedCodeApiJwtMode() || isEnabled(process.env.CODEAPI_JWT_ENABLED);
}
async function getCodeApiAuthHeaders(req) {
if (!req || !isCodeApiJwtAuthEnabled()) return {}; // ← no API-key fallback
const token = await mintCodeApiToken(req);
return token ? { Authorization: `Bearer ${token}` } : {};
}
process.env.LIBRECHAT_CODE_API_KEY appears nowhere in this function or its callees. There are exactly two possible return values: {} when JWT auth is off, or { Authorization: "Bearer <jwt>" } when it is on. Neither carries x-api-key.
That empty object is spread into every code-API request. @librechat/agents/dist/cjs/tools/CodeExecutor.cjs:
const fetchOptions = {
method: "POST",
headers: {
"Content-Type": "application/json",
"User-Agent": "LibreChat/1.0",
...await resolveCodeApiAuthHeaders(authHeaders) // ← {} or { Authorization: Bearer … }
},
body: JSON.stringify(postData)
};
So the request goes out with either only Content-Type and User-Agent, or those plus a Bearer JWT. An interpreter expecting x-api-key returns 401 in both cases, which is then mapped to a generic message:
if (response.status === 401 || response.status === 403)
return CODE_API_AUTHORIZATION_ERROR_MESSAGE;
// 'Code execution is not authorized. Verify access before trying again.'
The same getCodeApiAuthHeaders is used for the file paths in api/server/services/Files/Code/crud.js (download, delete, upload), so file operations are affected identically.
Proof: a real minted token is rejected
The token below was minted by LibreChat itself, extracted by calling the exported function directly, then replayed against the interpreter:
TOKEN=$(docker compose exec -T api node -e "
const api=require('/app/packages/api/dist/index.cjs');
api.getCodeApiAuthHeaders({user:{id:'<userId>',role:'ADMIN'},authStrategy:'local'})
.then(h=>process.stdout.write(h.Authorization.replace('Bearer ','')));
")
curl -i -s -X POST http://<interpreter>:8000/exec \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $TOKEN" \
-d '{"lang":"py","code":"print(1)"}'
HTTP/1.1 401 Unauthorized
{"error":"API key is required"}
The same interpreter, same endpoint, with -H "x-api-key: $LIBRECHAT_CODE_API_KEY" instead executes normally — {"lang":"py","code":"print(2+2)"} returns {"stdout":"4\n","stderr":"","files":[]}.
Minting works correctly. Calling getCodeApiAuthHeaders directly returns a valid EdDSA JWT:
{
"iss": "librechat", "aud": "codeapi", "sub": "<userId>",
"tenant_id": "default", "role": "ADMIN",
"principal_source": "librechat_jwt",
"auth_context_hash": "414e0ae8…"
}
(header: {"alg":"EdDSA","typ":"JWT","kid":"lc-codeapi-2026-05"})
So the failure is not misconfiguration on either side. LibreChat sends a Bearer JWT; the interpreter — written to the documented x-api-key contract — rejects it. There is no configuration that makes v0.8.7 send x-api-key.
Steps to reproduce
- Deploy any
codeapi-compatible interpreter that authenticates with x-api-key.
- Set
LIBRECHAT_CODE_BASEURL and LIBRECHAT_CODE_API_KEY in .env.
- Create an agent with Run Code enabled and send
print("hello").
- LibreChat logs
Code execution is not authorized. Verify access before trying again.
- The interpreter 401s. A rejected request is not logged as a failed execution, so from the interpreter side the service appears idle — which is what makes this hard to diagnose.
Both auth configurations fail, for different reasons. I tried each:
| Configuration |
What LibreChat sends |
Result |
No CODEAPI_* variables set |
(no auth header at all) |
401 |
CODEAPI_AUTH_PROVIDER=librechat-jwt + valid ed25519 key |
Authorization: Bearer <JWT> |
401 |
The second case is worth stressing: minting succeeds and a well-formed token is sent, but an interpreter built to the documented contract has nothing to verify it against. There is no configuration under which v0.8.7 sends x-api-key.
Who this breaks
This is not a hypothetical. The codeapi contract has independent implementations built against the published docs, and the ones using x-api-key are broken by this change:
The one implementation that survives the change is the one that reverse-engineered the JWT scheme. Anyone following the current documentation builds a server LibreChat cannot authenticate to.
I have opened a companion request on LibreCodeInterpreter asking it to verify LibreChat-minted JWTs (see link at top). But that only fixes one interpreter — the docs would still describe a contract LibreChat does not implement, and every future codeapi server written from them would hit the same wall.
Expected behavior
Either of:
getCodeApiAuthHeaders falls back to { 'x-api-key': process.env.LIBRECHAT_CODE_API_KEY } when JWT auth is not enabled — matching the documented behaviour; or
- the docs are updated to state that self-hosted deployments now require the
CODEAPI_JWT_* configuration, with the required variables listed and an example of what the interpreter must do to verify.
The first seems preferable: it keeps the documented path working, and the JWT mode remains available for deployments that want per-user claims. A two-line fallback would do it:
async function getCodeApiAuthHeaders(req) {
if (!req || !isCodeApiJwtAuthEnabled()) {
const key = process.env.LIBRECHAT_CODE_API_KEY;
return key ? { 'x-api-key': key } : {};
}
const token = await mintCodeApiToken(req);
return token ? { Authorization: `Bearer ${token}` } : {};
}
If the JWT scheme is intended to fully replace API keys, then documenting it is the equivalent fix — the CODEAPI_JWT_* variables appear in neither the Code Interpreter docs nor .env.example, so third-party interpreters currently have no way to implement verification except by reading the compiled bundle, which is how I arrived at the claim structure above.
Secondary: the error message sends you to the wrong subsystem
Code execution is not authorized. Verify access before trying again. reads as a permissions failure. I spent a long time in the RBAC system before looking at headers — verifying RUN_CODE: { USE: true } on both roles, confirming interface: runCode: true parsed at startup, checking the agent capability, and inspecting the roles collection directly. All were correct; none were relevant.
Surfacing the status code — e.g. Code execution request rejected (HTTP 401) — would point at the transport instead. buildCodeApiHttpErrorMessage already has response.status in hand.
Third issue: mint failures are undiagnosable
resolveCodeApiAuthHeaders discards the original error:
if (typeof authHeaders === 'function') try {
return await authHeaders();
} catch {
throw new CodeApiRequestError(CODE_API_AUTHORIZATION_ERROR_MESSAGE); // original error lost
}
mintCodeApiToken can throw for several distinct reasons — "Code API JWT signing key is not configured", "Unsupported Code API JWT algorithm", "Code API JWT auth requires tenant context", "Code API token minting requires an authenticated user", "Code API token minting requires a canonical user id" — and every one of them surfaces as the same generic authorization message, with nothing logged.
In my case the mint was not failing — I established that only by calling getCodeApiAuthHeaders directly from node -e to bypass this catch. Until then I had no way to distinguish "minting threw" from "the interpreter rejected the token", because both produce the identical message with nothing logged. Logging the caught error at debug level would make JWT misconfiguration self-diagnosing.
Undocumented configuration surface
For anyone else hitting this, the JWT path reads (all from getSigningConfig, isCodeApiJwtAuthEnabled, and resolveSingleTenantId):
| Variable |
Notes |
CODEAPI_AUTH_PROVIDER |
librechat-jwt or both |
CODEAPI_JWT_ENABLED |
alternative toggle |
CODEAPI_JWT_PRIVATE_KEY |
PEM; \n escapes are normalised |
CODEAPI_JWT_PRIVATE_KEY_BASE64 |
base64-encoded PEM |
CODEAPI_JWT_PRIVATE_JWK_JSON |
JWK JSON |
CODEAPI_JWT_ALGORITHM |
EdDSA (default) or RS256; anything else throws |
CODEAPI_JWT_KID / CODEAPI_JWT_KEY_ID |
defaults to DEFAULT_KID |
CODEAPI_JWT_ISSUER |
defaults to DEFAULT_ISSUER |
CODEAPI_JWT_AUDIENCE |
defaults to DEFAULT_AUDIENCE |
CODEAPI_JWT_TTL_SECONDS |
capped |
CODEAPI_JWT_MINT_CACHE_SECONDS |
capped |
CODEAPI_JWT_SINGLE_TENANT_ID |
defaults to DEFAULT_SINGLE_TENANT_ID |
None of these appear in .env.example or the Code Interpreter documentation. Documenting them — along with the claim structure interpreters must verify — would let third-party codeapi implementations support the new scheme.
Workaround
For anyone blocked by this, a header-rewriting proxy in front of the interpreter restores the documented behaviour. Caddy, on the interpreter host:
:8080 {
reverse_proxy localhost:8000 {
header_up -Authorization
header_up x-api-key "<your key>"
}
}
Point LIBRECHAT_CODE_BASEURL at :8080. Verified working end to end — multi-file upload, execution, file generation, and inline rendering all function normally through it.
Version information
- LibreChat v0.8.7,
registry.librechat.ai/danny-avila/librechat-dev:latest
- Deployment: Docker Compose
- Interpreter:
usnavy13/LibreCodeInterpreter v1.2.0, a third-party codeapi-compatible implementation authenticating via x-api-key
- Model:
gpt-5.6-terra, Responses API enabled on the agent
What was ruled out first
| Check |
Result |
| Env vars present in the container |
docker compose exec api node -e "console.log(process.env.LIBRECHAT_CODE_BASEURL)" → correct URL; key also present |
| Network from inside the api container |
fetch() to the interpreter's /health → healthy |
RUN_CODE permission |
{ USE: true } on both ADMIN and USER |
interface: runCode: true |
present in the parsed startup config |
| Agent capability |
Run Code enabled and saved |
| Responses API |
enabled per-agent; the separate 400 Function tools with reasoning_effort… error resolved by it |
| Interpreter with a correct key |
curl -H "x-api-key: $KEY" .../exec -d '{"lang":"py","code":"print(2+2)"}' → {"stdout":"4\n"} |
| Interpreter without a key |
{"error":"API key is required"} — reproduces the failure exactly |
Relevant log output
error: Error calling tool Execution error:
Code execution is not authorized. Verify access before trying again.
{"userId":"...","request_path":"/api/agents"}
error: [ON_TOOL_EXECUTE] Tool bash_tool error: Execution error:
Code execution is not authorized. Verify access before trying again.
Possibly related
Notes
The interpreter side is healthy and not in question — direct curl with x-api-key executes Python, writes to /mnt/data, and returns registered file objects. The only failure is the missing auth header on LibreChat's outbound request.
Happy to test a patch or provide further logs.
On the companion issue: I filed both sides in parallel rather than picking one, because either change alone restores interoperability and it isn't my call which project should absorb it. If the intent is that x-api-key is deprecated and JWT is the path forward, say so on either thread and I'll carry that back — the interpreter maintainer would then have a clear signal to implement verification, and the docs update becomes the fix on this side.
Appendix: full configuration
All secrets replaced with <REDACTED>; variable names preserved.
LibreChat — librechat.yaml
version: 1.2.8
cache: true
interface:
runCode: true
endpoints:
agents:
capabilities: ["execute_code", "file_search", "artifacts", "tools", "actions", "context", "ocr"]
openAI:
models:
default: ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"]
fetch: false
titleModel: "gpt-5.6-luna"
modelSpecs:
enforce: false
list:
- name: "gpt-5-6-terra"
label: "GPT-5.6 Terra"
softDefault: true
preset:
endpoint: "openAI"
model: "gpt-5.6-terra"
useResponsesApi: true
- name: "gpt-5-6-sol"
label: "GPT-5.6 Sol"
preset:
endpoint: "openAI"
model: "gpt-5.6-sol"
useResponsesApi: true
- name: "gpt-5-6-luna"
label: "GPT-5.6 Luna"
preset:
endpoint: "openAI"
model: "gpt-5.6-luna"
useResponsesApi: true
LibreChat — docker-compose.override.yml
services:
api:
volumes:
- type: bind
source: ./librechat.yaml
target: /app/librechat.yaml
environment:
- LIBRECHAT_CODE_BASEURL=${LIBRECHAT_CODE_BASEURL}
- LIBRECHAT_CODE_API_KEY=${LIBRECHAT_CODE_API_KEY}
- CODEAPI_AUTH_PROVIDER=${CODEAPI_AUTH_PROVIDER}
- CODEAPI_JWT_PRIVATE_KEY=${CODEAPI_JWT_PRIVATE_KEY}
- CODEAPI_JWT_SINGLE_TENANT_ID=${CODEAPI_JWT_SINGLE_TENANT_ID}
meilisearch:
profiles:
- donotstart
admin-panel:
ports:
- "3001:3000"
environment:
- SESSION_SECRET=${SESSION_SECRET}
The explicit environment: block is required — LibreChat's base docker-compose.yml passes an allow-list to the api container, and LIBRECHAT_CODE_* / CODEAPI_* are not on it. Without this they are silently dropped and process.env.LIBRECHAT_CODE_BASEURL is undefined inside the container despite being correct in .env.
LibreChat — .env (relevant lines)
SEARCH=false
ALLOW_REGISTRATION=true
OPENAI_API_KEY=<REDACTED>
UID=1000
GID=1000
LIBRECHAT_CODE_BASEURL=http://10.250.236.53:8080 # 8080 = auth shim; 8000 = interpreter
LIBRECHAT_CODE_API_KEY=<REDACTED>
CODEAPI_AUTH_PROVIDER=librechat-jwt
CODEAPI_JWT_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n<REDACTED ed25519>\n-----END PRIVATE KEY-----\n"
CODEAPI_JWT_SINGLE_TENANT_ID=default
CREDS_KEY=<REDACTED>
CREDS_IV=<REDACTED>
JWT_SECRET=<REDACTED>
JWT_REFRESH_SECRET=<REDACTED>
SESSION_SECRET=<REDACTED>
Note: this is the current working state, so LIBRECHAT_CODE_BASEURL points at the shim on :8080. To reproduce the bug, point it at the interpreter directly on :8000.
Both auth paths are configured simultaneously. LIBRECHAT_CODE_API_KEY is present and correct; it is simply never sent. The CODEAPI_* variables are also set and minting works — the token is just unverifiable by the interpreter.
Interpreter — .env
API_KEY=<REDACTED>
REDIS_HOST=localhost
REDIS_PORT=6379
S3_ENDPOINT=localhost:3900
S3_ACCESS_KEY=<REDACTED>
S3_SECRET_KEY=<REDACTED>
The entire auth surface is API_KEY. There is no issuer, audience, algorithm, or public-key setting — nothing a JWT could be verified against.
Interpreter — docker-compose.yml
services:
api:
image: ${API_IMAGE:-ghcr.io/usnavy13/librecodeinterpreter:main}
pull_policy: always
container_name: code-interpreter-api
restart: unless-stopped
init: true
cap_add:
- SYS_ADMIN
- NET_ADMIN
security_opt:
- apparmor:unconfined
ports:
- "${PORT:-8000}:8000"
env_file:
- .env
environment:
- REDIS_HOST=redis
- S3_ENDPOINT=garage:3900
volumes:
- sandbox-data:/var/lib/code-interpreter/sandboxes
- skill-deps:/opt/skill-deps
- ${SSL_CERTS_PATH:-./ssl}:/app/ssl:ro
tmpfs:
- /tmp:size=512m,mode=1777,noexec,nosuid,nodev
- /app/data:size=100m
depends_on:
redis: { condition: service_healthy }
garage: { condition: service_healthy }
healthcheck:
test: ["CMD-SHELL", "curl -fs http://localhost:8000/health || curl -fsk https://localhost:8000/health"]
interval: 30s
timeout: 15s
retries: 3
start_period: 30s
redis:
image: redis:7-alpine
ports: ["127.0.0.1:${REDIS_PORT:-6379}:6379"]
# …appendonly, 256mb maxmemory, healthcheck
garage:
image: dxflrs/garage:v2.3.0
command: /garage server --single-node --default-bucket
ports:
- "127.0.0.1:${S3_PORT:-3900}:3900"
- "127.0.0.1:${GARAGE_ADMIN_PORT:-3903}:3903"
environment:
GARAGE_DEFAULT_ACCESS_KEY: ${S3_ACCESS_KEY:-<REDACTED>}
GARAGE_DEFAULT_SECRET_KEY: ${S3_SECRET_KEY:-<REDACTED>}
GARAGE_DEFAULT_BUCKET: ${S3_BUCKET:-code-interpreter-files}
Only the API binds 0.0.0.0; Redis and Garage are loopback-only. No JWT configuration exists anywhere in the stack.
The auth shim — Caddyfile
Running in the same VM, on port 8080:
:8080 {
reverse_proxy localhost:8000 {
header_up -Authorization
header_up x-api-key "<REDACTED>"
}
}
docker run -d --name codeshim --network host \
-v ~/codeshim/Caddyfile:/etc/caddy/Caddyfile:ro \
--restart unless-stopped caddy:2-alpine
Verification that it is the header, and only the header:
# direct to the interpreter, with a Bearer token → rejected
curl -i -X POST http://10.250.236.53:8000/exec -H 'Content-Type: application/json' \
-H "Authorization: Bearer <real LibreChat-minted JWT>" \
-d '{"lang":"py","code":"print(1)"}'
# HTTP/1.1 401 Unauthorized
# {"error":"API key is required"}
# through the shim, with a deliberately invalid Bearer token → accepted
curl -i -X POST http://10.250.236.53:8080/exec -H 'Content-Type: application/json' \
-H "Authorization: Bearer garbage" \
-d '{"lang":"py","code":"print(1)"}'
# HTTP/1.1 200 OK
With LIBRECHAT_CODE_BASEURL pointed at :8080, everything works — agent-driven execution, multi-file upload, file generation, and inline rendering.
Version Information
LibreChat version: v0.8.7
Commit: d89b11d
Branch: main
Build date: 2026-08-11 03:14:37 UTC
User agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36 OPR/134.0.0.0
Steps to Reproduce
Full detail in the main report, to reproduce, and verify the fix
shim workaround verified to work.
What browsers are you seeing the problem on?
Chrome
Relevant log output
I can provide as much as you want but I think the main report is complete and exhaustive
Screenshots
screenshots of multi-file upload and code execution working after shim
model is 3.6 terra. not shown in "select a model" because agent toolman selects one.
running as agent "toolman" here
failure before hand would either not show the "run code" choice because the llm was emulating the print. or there would be a red popup saying it was unable to execute code.
be sure you are always using and agent with the "run code" tool. I disabled web search so only one tool in use ..run code..just to avoid questions
looks like i have it back on for toolman. so two tools may no longer cause an issue (it was a reported issue in the past)
here's the setup for agent toolman. I used the green "select" on a conv to get the agent but you can also go in the agent marketplace and click a big toolman and get a conv
Code of Conduct
What happened?
What happened?
With a self-hosted code interpreter configured per the Code Interpreter docs —
LIBRECHAT_CODE_BASEURL+LIBRECHAT_CODE_API_KEY— every execution fails, and reading the shipped code shows why:LIBRECHAT_CODE_API_KEYis never read when building the outbound request headers.The docs describe the env var as the global setup path:
But in v0.8.7,
x-api-keyis never attached.Root cause (from the shipped bundle)
/app/packages/api/dist/index.cjs:process.env.LIBRECHAT_CODE_API_KEYappears nowhere in this function or its callees. There are exactly two possible return values:{}when JWT auth is off, or{ Authorization: "Bearer <jwt>" }when it is on. Neither carriesx-api-key.That empty object is spread into every code-API request.
@librechat/agents/dist/cjs/tools/CodeExecutor.cjs:So the request goes out with either only
Content-TypeandUser-Agent, or those plus a Bearer JWT. An interpreter expectingx-api-keyreturns 401 in both cases, which is then mapped to a generic message:The same
getCodeApiAuthHeadersis used for the file paths inapi/server/services/Files/Code/crud.js(download, delete, upload), so file operations are affected identically.Proof: a real minted token is rejected
The token below was minted by LibreChat itself, extracted by calling the exported function directly, then replayed against the interpreter:
The same interpreter, same endpoint, with
-H "x-api-key: $LIBRECHAT_CODE_API_KEY"instead executes normally —{"lang":"py","code":"print(2+2)"}returns{"stdout":"4\n","stderr":"","files":[]}.Minting works correctly. Calling
getCodeApiAuthHeadersdirectly returns a valid EdDSA JWT:{ "iss": "librechat", "aud": "codeapi", "sub": "<userId>", "tenant_id": "default", "role": "ADMIN", "principal_source": "librechat_jwt", "auth_context_hash": "414e0ae8…" }(header:
{"alg":"EdDSA","typ":"JWT","kid":"lc-codeapi-2026-05"})So the failure is not misconfiguration on either side. LibreChat sends a Bearer JWT; the interpreter — written to the documented
x-api-keycontract — rejects it. There is no configuration that makes v0.8.7 sendx-api-key.Steps to reproduce
codeapi-compatible interpreter that authenticates withx-api-key.LIBRECHAT_CODE_BASEURLandLIBRECHAT_CODE_API_KEYin.env.print("hello").Code execution is not authorized. Verify access before trying again.Both auth configurations fail, for different reasons. I tried each:
CODEAPI_*variables setCODEAPI_AUTH_PROVIDER=librechat-jwt+ valid ed25519 keyAuthorization: Bearer <JWT>The second case is worth stressing: minting succeeds and a well-formed token is sent, but an interpreter built to the documented contract has nothing to verify it against. There is no configuration under which v0.8.7 sends
x-api-key.Who this breaks
This is not a hypothetical. The
codeapicontract has independent implementations built against the published docs, and the ones usingx-api-keyare broken by this change:x-api-keyonlyCODEAPI_JWT_*verificationThe one implementation that survives the change is the one that reverse-engineered the JWT scheme. Anyone following the current documentation builds a server LibreChat cannot authenticate to.
I have opened a companion request on LibreCodeInterpreter asking it to verify LibreChat-minted JWTs (see link at top). But that only fixes one interpreter — the docs would still describe a contract LibreChat does not implement, and every future
codeapiserver written from them would hit the same wall.Expected behavior
Either of:
getCodeApiAuthHeadersfalls back to{ 'x-api-key': process.env.LIBRECHAT_CODE_API_KEY }when JWT auth is not enabled — matching the documented behaviour; orCODEAPI_JWT_*configuration, with the required variables listed and an example of what the interpreter must do to verify.The first seems preferable: it keeps the documented path working, and the JWT mode remains available for deployments that want per-user claims. A two-line fallback would do it:
If the JWT scheme is intended to fully replace API keys, then documenting it is the equivalent fix — the
CODEAPI_JWT_*variables appear in neither the Code Interpreter docs nor.env.example, so third-party interpreters currently have no way to implement verification except by reading the compiled bundle, which is how I arrived at the claim structure above.Secondary: the error message sends you to the wrong subsystem
Code execution is not authorized. Verify access before trying again.reads as a permissions failure. I spent a long time in the RBAC system before looking at headers — verifyingRUN_CODE: { USE: true }on both roles, confirminginterface: runCode: trueparsed at startup, checking the agent capability, and inspecting therolescollection directly. All were correct; none were relevant.Surfacing the status code — e.g.
Code execution request rejected (HTTP 401)— would point at the transport instead.buildCodeApiHttpErrorMessagealready hasresponse.statusin hand.Third issue: mint failures are undiagnosable
resolveCodeApiAuthHeadersdiscards the original error:mintCodeApiTokencan throw for several distinct reasons —"Code API JWT signing key is not configured","Unsupported Code API JWT algorithm","Code API JWT auth requires tenant context","Code API token minting requires an authenticated user","Code API token minting requires a canonical user id"— and every one of them surfaces as the same generic authorization message, with nothing logged.In my case the mint was not failing — I established that only by calling
getCodeApiAuthHeadersdirectly fromnode -eto bypass this catch. Until then I had no way to distinguish "minting threw" from "the interpreter rejected the token", because both produce the identical message with nothing logged. Logging the caught error at debug level would make JWT misconfiguration self-diagnosing.Undocumented configuration surface
For anyone else hitting this, the JWT path reads (all from
getSigningConfig,isCodeApiJwtAuthEnabled, andresolveSingleTenantId):CODEAPI_AUTH_PROVIDERlibrechat-jwtorbothCODEAPI_JWT_ENABLEDCODEAPI_JWT_PRIVATE_KEY\nescapes are normalisedCODEAPI_JWT_PRIVATE_KEY_BASE64CODEAPI_JWT_PRIVATE_JWK_JSONCODEAPI_JWT_ALGORITHMEdDSA(default) orRS256; anything else throwsCODEAPI_JWT_KID/CODEAPI_JWT_KEY_IDDEFAULT_KIDCODEAPI_JWT_ISSUERDEFAULT_ISSUERCODEAPI_JWT_AUDIENCEDEFAULT_AUDIENCECODEAPI_JWT_TTL_SECONDSCODEAPI_JWT_MINT_CACHE_SECONDSCODEAPI_JWT_SINGLE_TENANT_IDDEFAULT_SINGLE_TENANT_IDNone of these appear in
.env.exampleor the Code Interpreter documentation. Documenting them — along with the claim structure interpreters must verify — would let third-partycodeapiimplementations support the new scheme.Workaround
For anyone blocked by this, a header-rewriting proxy in front of the interpreter restores the documented behaviour. Caddy, on the interpreter host:
Point
LIBRECHAT_CODE_BASEURLat:8080. Verified working end to end — multi-file upload, execution, file generation, and inline rendering all function normally through it.Version information
registry.librechat.ai/danny-avila/librechat-dev:latestusnavy13/LibreCodeInterpreterv1.2.0, a third-partycodeapi-compatible implementation authenticating viax-api-keygpt-5.6-terra, Responses API enabled on the agentWhat was ruled out first
docker compose exec api node -e "console.log(process.env.LIBRECHAT_CODE_BASEURL)"→ correct URL; key also presentfetch()to the interpreter's/health→ healthyRUN_CODEpermission{ USE: true }on both ADMIN and USERinterface: runCode: true400 Function tools with reasoning_effort…error resolved by itcurl -H "x-api-key: $KEY" .../exec -d '{"lang":"py","code":"print(2+2)"}'→{"stdout":"4\n"}{"error":"API key is required"}— reproduces the failure exactlyRelevant log output
Possibly related
CodeExecutor.tsomittingx-api-keyentirely. That specific omission is fixed;resolveCodeApiAuthHeadersnow exists and is spread into the headers. But it resolves to{}under default configuration, so the observable behaviour is the same.Notes
The interpreter side is healthy and not in question — direct
curlwithx-api-keyexecutes Python, writes to/mnt/data, and returns registered file objects. The only failure is the missing auth header on LibreChat's outbound request.Happy to test a patch or provide further logs.
On the companion issue: I filed both sides in parallel rather than picking one, because either change alone restores interoperability and it isn't my call which project should absorb it. If the intent is that
x-api-keyis deprecated and JWT is the path forward, say so on either thread and I'll carry that back — the interpreter maintainer would then have a clear signal to implement verification, and the docs update becomes the fix on this side.Appendix: full configuration
All secrets replaced with
<REDACTED>; variable names preserved.LibreChat —
librechat.yamlLibreChat —
docker-compose.override.ymlLibreChat —
.env(relevant lines)Interpreter —
.envThe entire auth surface is
API_KEY. There is no issuer, audience, algorithm, or public-key setting — nothing a JWT could be verified against.Interpreter —
docker-compose.ymlThe auth shim —
CaddyfileRunning in the same VM, on port 8080:
docker run -d --name codeshim --network host \ -v ~/codeshim/Caddyfile:/etc/caddy/Caddyfile:ro \ --restart unless-stopped caddy:2-alpineVerification that it is the header, and only the header:
With
LIBRECHAT_CODE_BASEURLpointed at:8080, everything works — agent-driven execution, multi-file upload, file generation, and inline rendering.Version Information
LibreChat version: v0.8.7
Commit: d89b11d
Branch: main
Build date: 2026-08-11 03:14:37 UTC
User agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36 OPR/134.0.0.0
Steps to Reproduce
Full detail in the main report, to reproduce, and verify the fix
shim workaround verified to work.
What browsers are you seeing the problem on?
Chrome
Relevant log output
I can provide as much as you want but I think the main report is complete and exhaustiveScreenshots
screenshots of multi-file upload and code execution working after shim
model is 3.6 terra. not shown in "select a model" because agent toolman selects one.
running as agent "toolman" here
failure before hand would either not show the "run code" choice because the llm was emulating the print. or there would be a red popup saying it was unable to execute code.
be sure you are always using and agent with the "run code" tool. I disabled web search so only one tool in use ..run code..just to avoid questions
looks like i have it back on for toolman. so two tools may no longer cause an issue (it was a reported issue in the past)
here's the setup for agent toolman. I used the green "select" on a conv to get the agent but you can also go in the agent marketplace and click a big toolman and get a conv
Code of Conduct