You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Following up on #335, which was fixed by #338. With Moat 0.5.1 (built 2026-04-28, includes #338), the
original failure mode is gone — credential loading no longer 500s — but a new failure replaces it: the
proxy daemon never registers runs that declare MCP servers, so the relay endpoint serves 404 for every
request.
The agent's ~/.claude.json looks correct, the run metadata correctly includes the auto-included mcp-*
grants from appendMCPGrants(), but ~/.moat/proxy/runs.json is never updated to include the new run,
and the relay can't route to it.
This appears to be a regression in the run-registration path, distinct from the credential-loading path #338 fixed. Symptoms are similar enough to #335 that it could easily be mistaken for the same bug, but
the error wording and diagnostic evidence are different.
Environment
$ moat version
moat 0.5.1
commit: fc0596858df7275a341e3ca195860cd773c4e564
built: 2026-04-28T22:14:27Z
go: go1.25.5
Runtime: apple containers (macOS, arm64)
Reproduces with both Render MCP (mcp.render.com/mcp) and Linear MCP (mcp.linear.app/mcp) — same as
the original #335 repro patient.
Steps to reproduce
Store credentials for both MCP servers (one-time):
moat grant mcp render
moat grant mcp linear
Use this moat.yaml (minimal — adapted from a real project's config; the runtime: apple and
Playwright bits aren't load-bearing for this bug):
=== render ===
HTTP/1.1 404 Not Found
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Date: Wed, 29 Apr 2026 19:34:27 GMT
Content-Length: 139
BODY: MOAT: MCP server '<TOKEN>' not configured. Available servers: 2. Check moat.yaml.
=== linear ===
HTTP/1.1 404 Not Found
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Content-Length: 139
BODY: MOAT: MCP server '<TOKEN>' not configured. Available servers: 2. Check moat.yaml.
The relay handler appears to be parsing the first path segment (<TOKEN>) as the server name, rather
than treating it as the per-run auth token and using the second segment (render / linear) as the
server name. The "Available servers: 2" hint suggests the daemon does know there are two configured MCP
servers, but is looking them up by the wrong key.
Expected result
HTTP/1.1 200 OK (or 202) with the upstream MCP server's initialize response — same as the
documented contract for the relay.
`MOAT: Failed to load credential for 'render'. Grant: mcp-render. Run: moat grant mcp
render`
MOAT: MCP server '<auth-token>' not configured. Available servers: 2. Check moat.yaml.
Server name in error
Correct ('render')
The auth token ('<32-byte hex>')
~/.moat/proxy/runs.json
Run + mcp_servers registered
Run never appears at all
Step that fails
Credential lookup for known server
Server lookup itself; run-registration with
proxy daemon
#338 fixed the credential-loading half of the path (appendMCPGrants correctly merges mcp[].auth.grant
into the run context). This issue is one step earlier in the stack — the proxy daemon's per-run
registration of MCP server endpoints.
Diagnostic evidence
1. Run metadata is correct — appendMCPGrants did its job
Both mcp-render and mcp-linear are present — they were added by #338's auto-include behavior, exactly
as designed. So the bug is not in the run-context layer.
2. Proxy daemon's runs.json does not contain the new run
After running moat claude . and observing the 404 above, ~/.moat/proxy/runs.json (file mtime updated
to the same minute as the run) contains only 4 unrelated old runs (none of them this run, none of
them with any mcp_servers key):
The new run's auth token (the <TOKEN> shown by the relay's 404 message and visible in ~/.claude.json
inside the container) is not in this list. None of the four entries have any mcp_servers field. The
daemon is actively writing to this file (mtime updates on each run start) but new runs with MCP grants
are not being added.
3. Restarting the daemon does not help
$ pgrep -af "moat _daemon"
88204 /opt/homebrew/bin/moat _daemon --dir /Users/me/.moat/proxy --proxy-port 19080
$ kill 88204
$ moat claude .# respawns the daemon and starts a fresh run# … run a new probe …# Same 404, with a different <TOKEN> from the new run, still not in runs.json
The daemon respawns cleanly; new runs trigger a runs.json write at the same timestamp; but the new run
is never present in the file. The four old entries are simply rewritten unchanged.
4. Working comparison: prepackaged sandbox-local stdio MCP servers are unaffected
claude.mcp.<name> stdio servers (e.g., playwright) work correctly, since they don't go through the
relay. The break is isolated to the top-level remote mcp: registration path.
5. There is no daemon.log output
~/.moat/proxy/daemon.log is zero bytes (it has been since first install per its mtime), so there's no
daemon-side error trace I can include.
Things I tried
Re-running moat grant mcp render / moat grant mcp linear — no change. moat grant show confirms
both creds are encrypted and on disk.
Killing moat _daemon and letting it respawn — no change. Fresh daemon, same omission.
Running multiple fresh moat claude . sessions back-to-back — every run produces a new auth token,
none of them appear in runs.json.
Verifying the new run does receive the MCP grants in its run metadata (point 1 above) — it does, so
the issue is downstream of appendMCPGrants.
Summary
Following up on #335, which was fixed by #338. With Moat 0.5.1 (built 2026-04-28, includes #338), the
original failure mode is gone — credential loading no longer 500s — but a new failure replaces it: the
proxy daemon never registers runs that declare MCP servers, so the relay endpoint serves
404for everyrequest.
The agent's
~/.claude.jsonlooks correct, the run metadata correctly includes the auto-includedmcp-*grants from
appendMCPGrants(), but~/.moat/proxy/runs.jsonis never updated to include the new run,and the relay can't route to it.
This appears to be a regression in the run-registration path, distinct from the credential-loading path
#338 fixed. Symptoms are similar enough to #335 that it could easily be mistaken for the same bug, but
the error wording and diagnostic evidence are different.
Environment
Reproduces with both Render MCP (
mcp.render.com/mcp) and Linear MCP (mcp.linear.app/mcp) — same asthe original #335 repro patient.
Steps to reproduce
moat.yaml(minimal — adapted from a real project's config; theruntime: appleandPlaywright bits aren't load-bearing for this bug):
moat claude .Output (looks correct):
mcp:failing to load staticmcp-*credentials in Apple container #335 repro:Actual result
The relay handler appears to be parsing the first path segment (
<TOKEN>) as the server name, ratherthan treating it as the per-run auth token and using the second segment (
render/linear) as theserver name. The "Available servers: 2" hint suggests the daemon does know there are two configured MCP
servers, but is looking them up by the wrong key.
Expected result
HTTP/1.1 200 OK(or202) with the upstream MCP server'sinitializeresponse — same as thedocumented contract for the relay.
Why this is distinct from #335
Symptoms are similar (relay errors, MCP unusable from the agent), but the root cause is at a different
layer:
500 Internal Server Error404 Not FoundMOAT: MCP server '<auth-token>' not configured. Available servers: 2. Check moat.yaml.'render')'<32-byte hex>')~/.moat/proxy/runs.jsonmcp_serversregistered#338 fixed the credential-loading half of the path (
appendMCPGrantscorrectly mergesmcp[].auth.grantinto the run context). This issue is one step earlier in the stack — the proxy daemon's per-run
registration of MCP server endpoints.
Diagnostic evidence
1. Run metadata is correct —
appendMCPGrantsdid its jobBoth
mcp-renderandmcp-linearare present — they were added by #338's auto-include behavior, exactlyas designed. So the bug is not in the run-context layer.
2. Proxy daemon's
runs.jsondoes not contain the new runAfter running
moat claude .and observing the 404 above,~/.moat/proxy/runs.json(file mtime updatedto the same minute as the run) contains only 4 unrelated old runs (none of them this run, none of
them with any
mcp_serverskey):{ "version": 1, "runs": [ { "auth_token": "8c318ee0…", "run_id": "run_5d11fba44e37", "grants": ["claude", "github"], … }, { "auth_token": "d4331ab5…", "run_id": "run_b9bb27801818", "grants": ["claude"], … }, { "auth_token": "965d1683…", "run_id": "run_35698067493b", "grants": ["claude"], … }, { "auth_token": "d0605162…", "run_id": "run_ca83d19109c5", "grants": ["claude"], … } ] }The new run's auth token (the
<TOKEN>shown by the relay's 404 message and visible in~/.claude.jsoninside the container) is not in this list. None of the four entries have any
mcp_serversfield. Thedaemon is actively writing to this file (mtime updates on each run start) but new runs with MCP grants
are not being added.
3. Restarting the daemon does not help
The daemon respawns cleanly; new runs trigger a
runs.jsonwrite at the same timestamp; but the new runis never present in the file. The four old entries are simply rewritten unchanged.
4. Working comparison: prepackaged sandbox-local stdio MCP servers are unaffected
claude.mcp.<name>stdio servers (e.g.,playwright) work correctly, since they don't go through therelay. The break is isolated to the top-level remote
mcp:registration path.5. There is no
daemon.logoutput~/.moat/proxy/daemon.logis zero bytes (it has been since first install per its mtime), so there's nodaemon-side error trace I can include.
Things I tried
moat grant mcp render/moat grant mcp linear— no change.moat grant showconfirmsboth creds are encrypted and on disk.
moat _daemonand letting it respawn — no change. Fresh daemon, same omission.moat claude .sessions back-to-back — every run produces a new auth token,none of them appear in
runs.json.the issue is downstream of
appendMCPGrants.