Moxn glass write path down: owl-glass MCP rejects all calls with Bad Request #1026

Closed
opened 2026-08-19 02:23:54 +00:00 by coilyco-ops-gaming · 10 comments

The moxn MCP endpoint for the owl-glass workspace is rejecting every call at the transport level, so the glass write path is unavailable.

Observed

A request to create a small rehearsal document in the glass filesystem (outside /publish) failed on the very first call. moxn find over the glass filesystem returned, twice on retry:

  • calling "tools/list": Bad Request on the HTTP MCP transport
  • reconnect to https://owl-glass.moxn.dev/api/mcp/http also failed with Bad Request on notifications/initialized

The failure happens at tools/list / notifications/initialized, before any actual tool call is made, so no document was created and nothing was modified. Retrying once did not help.

Likely cause

This matches the documented expiry mode for this surface: the credential is minted by hand and nothing renews it, so on expiry the tools stay listed but every call fails. The transport-level Bad Request shape is consistent with a stale or expired auth token rather than a code defect.

What is blocked

The pre-stream rehearsal of the write path (create a doc in glass, confirm it did not reach /publish). Without a working write path, the demo's documented flow cannot be exercised before the stream.

Ask

Re-mint / renew the moxn credential for the owl-glass workspace (operator: whoever mints that credential, Coilyco ops side), then confirm a write call to glass returns clean. Also consider a renewal reminder or a failure signal earlier than the first call, since the outage is silent until someone tries to write.

The moxn MCP endpoint for the owl-glass workspace is rejecting every call at the transport level, so the glass write path is unavailable. ## Observed A request to create a small rehearsal document in the `glass` filesystem (outside `/publish`) failed on the very first call. `moxn find` over the `glass` filesystem returned, twice on retry: - `calling "tools/list": Bad Request` on the HTTP MCP transport - reconnect to `https://owl-glass.moxn.dev/api/mcp/http` also failed with `Bad Request` on `notifications/initialized` The failure happens at `tools/list` / `notifications/initialized`, before any actual tool call is made, so no document was created and nothing was modified. Retrying once did not help. ## Likely cause This matches the documented expiry mode for this surface: the credential is minted by hand and nothing renews it, so on expiry the tools stay listed but every call fails. The transport-level `Bad Request` shape is consistent with a stale or expired auth token rather than a code defect. ## What is blocked The pre-stream rehearsal of the write path (create a doc in glass, confirm it did not reach `/publish`). Without a working write path, the demo's documented flow cannot be exercised before the stream. ## Ask Re-mint / renew the moxn credential for the owl-glass workspace (operator: whoever mints that credential, Coilyco ops side), then confirm a write call to `glass` returns clean. Also consider a renewal reminder or a failure signal earlier than the first call, since the outage is silent until someone tries to write.
Member

Confirmed from the pod side, and adding the runbook plus one discrepancy worth fixing.

Independent evidence

sirens-dowel-moxn-mcp logs. The surface worked and then stopped:

2026-08-18T21:58:43Z INFO serving upstream proxy  server=moxn tools=9
2026-08-18T22:00:30Z INFO tool call served  tool=find    outcome=ok
2026-08-18T22:00:31Z INFO tool call served  tool=search  outcome=ok
2026-08-19T02:23:21Z WARN tool call refused tool=find    outcome=tool_error
    reason: refresh upstream tools: calling "tools/list": Bad Request
    (reconnect also failed: ... "notifications/initialized": Bad Request)

So it broke between 22:00:31Z and 02:23:21Z. The pod is not restarting and is still 1/1 Running, which is the documented shape: the tools stay listed and the failure only appears on use.

Worth noting those are the only four tool calls in the pod's life. The 02:23 entry is the rehearsal request that found this.

The runbook, from sirens-dowel-moxn-mcp-values.yaml

Re-seeding is two steps, and one alone does nothing:

  1. Mint a fresh token and write it to SSM /coilysiren/moxn/access-token. Moxn's MCP endpoint is OAuth authorization-code only, with no service token and no client_credentials grant, so this is an attended browser flow. It cannot be automated or done by an agent.
  2. kubectl -n sirens-dowel rollout restart deploy/sirens-dowel-moxn-mcp

Step 2 is required because the token reaches the container as an env var read once at start, and the ExternalSecret refreshes on its own hourly schedule. Writing the parameter reaches nothing already running.

Discrepancy worth correcting in the values file

The file predicts the expiry looks like 401 on every call. The observed failure is Bad Request on tools/list and on notifications/initialized, at the transport layer, before any tool call is attempted. Anyone grepping logs for 401 will not match this, and the reconnect line makes it read like a transport or endpoint fault rather than a credential one.

Lifetime arithmetic, since it decides when to mint

The token is documented as good for 24 hours from mint, not from pod start. This one was already working when the pod came up at 21:58Z and was dead by 02:23Z, under four and a half hours of pod life, so it was minted roughly twenty hours before that deployment.

Stream is 2026-08-19 11:00 to 11:50 PDT, which is 18:00 to 18:50 UTC. A token minted now covers it with several hours to spare. The real risk is not the window, it is minting without doing step 2, or minting and then rolling the pod again afterwards from an older secret.

  • mcp-beaver#82 is named in the values file as the durable fix and is deliberately not built.
  • #981 - this blocks a demo-readiness item; flagged there.
Confirmed from the pod side, and adding the runbook plus one discrepancy worth fixing. ## Independent evidence `sirens-dowel-moxn-mcp` logs. The surface worked and then stopped: ``` 2026-08-18T21:58:43Z INFO serving upstream proxy server=moxn tools=9 2026-08-18T22:00:30Z INFO tool call served tool=find outcome=ok 2026-08-18T22:00:31Z INFO tool call served tool=search outcome=ok 2026-08-19T02:23:21Z WARN tool call refused tool=find outcome=tool_error reason: refresh upstream tools: calling "tools/list": Bad Request (reconnect also failed: ... "notifications/initialized": Bad Request) ``` So it broke between **22:00:31Z and 02:23:21Z**. The pod is not restarting and is still `1/1 Running`, which is the documented shape: the tools stay listed and the failure only appears on use. Worth noting those are the only four tool calls in the pod's life. The 02:23 entry is the rehearsal request that found this. ## The runbook, from `sirens-dowel-moxn-mcp-values.yaml` Re-seeding is **two steps, and one alone does nothing**: 1. Mint a fresh token and write it to SSM `/coilysiren/moxn/access-token`. Moxn's MCP endpoint is OAuth authorization-code only, with no service token and no `client_credentials` grant, so this is an **attended browser flow**. It cannot be automated or done by an agent. 2. `kubectl -n sirens-dowel rollout restart deploy/sirens-dowel-moxn-mcp` Step 2 is required because the token reaches the container as an env var read once at start, and the ExternalSecret refreshes on its own hourly schedule. Writing the parameter reaches nothing already running. ## Discrepancy worth correcting in the values file The file predicts the expiry looks like **401 on every call**. The observed failure is **`Bad Request` on `tools/list` and on `notifications/initialized`**, at the transport layer, before any tool call is attempted. Anyone grepping logs for `401` will not match this, and the reconnect line makes it read like a transport or endpoint fault rather than a credential one. ## Lifetime arithmetic, since it decides when to mint The token is documented as good for **24 hours from mint**, not from pod start. This one was already working when the pod came up at 21:58Z and was dead by 02:23Z, under four and a half hours of pod life, so it was minted roughly twenty hours before that deployment. Stream is 2026-08-19 11:00 to 11:50 PDT, which is 18:00 to 18:50 UTC. A token minted now covers it with several hours to spare. The real risk is not the window, it is minting without doing step 2, or minting and then rolling the pod again afterwards from an older secret. ## Related - `mcp-beaver#82` is named in the values file as the durable fix and is deliberately not built. - #981 - this blocks a demo-readiness item; flagged there.
Author
Member

Still live as of 2026-08-19T02:38Z. The pre-stream live write test (an intro page destined for /publish) failed identically on the first call: moxn find over the glass filesystem returned "upstream MCP session is closed (reconnect also failed: sending notifications/initialized: Bad Request)". No document was created and nothing was modified. The /publish write path remains blocked until the credential is re-minted; the write test cannot land before the stream without it.

Still live as of 2026-08-19T02:38Z. The pre-stream live write test (an intro page destined for /publish) failed identically on the first call: `moxn find` over the glass filesystem returned "upstream MCP session is closed (reconnect also failed: sending notifications/initialized: Bad Request)". No document was created and nothing was modified. The /publish write path remains blocked until the credential is re-minted; the write test cannot land before the stream without it.
Member

Independent verification, and one thing that changes the fix

Dowel reported this from inside a turn. I checked the cluster from outside it, and the failure is real.

Confirmed

sirens-dowel-moxn-mcp logs, two refusals with the same shape:

02:23:21Z  tool call refused  find  refresh upstream tools: calling "tools/list": Bad Request
           (reconnect also failed: ... "notifications/initialized": Bad Request)
02:38:55Z  tool call refused  find  mcp-beaver: upstream MCP session is closed
           (reconnect also failed: ... same)

The 02:38:55Z entry is Dowel's own attempt at the live write test. The pod is Running 1/1 with 0 restarts, so this is credential rejection rather than a crash.

A restart on its own will not fix it

I compared the token the cluster holds against the current SSM value by fingerprint, without reading either:

  • /coilysiren/moxn/access-token (v3, written 2026-08-18T21:58Z) and the sirens-dowel-moxn-mcp-secret in the cluster are the same value.

So the ExternalSecret has already synced and the pod is holding SSM's current token. The token in SSM is itself the dead one. Re-minting is required; restarting or re-syncing alone reaches nothing.

The procedure already exists

deploy/scripts/refresh-moxn-token.sh, and its header carries the ordering constraint that makes this easy to get wrong:

THE EXTERNALSECRET MUST SYNC BEFORE THE RESTART, NOT AFTER. [...] minting revokes the old token immediately, so a restart against an unsynced Secret hands the pod a credential that is already dead and it crashloops on the startup dial for a 2m connect timeout per attempt. Observed on 2026-08-18.

It writes both SSM and the local ~/.moxn store on purpose, and takes --yes to act.

The part worth carrying into the prep hour

The script also records why this recurs:

Clerk keeps only the newest access token alive per (clientId, user), so minting one invalidates every sibling holding the old one.

So any Moxn call from a laptop between the prep-hour refresh and the stream will silently kill the pod again. The token is good for 24 hours, so a refresh at 10:00 PT covers an 11:50 finish with room. What it does not survive is a second mint.

I did not establish which mint revoked the current one. A 24-hour token written at 21:58Z should still be alive at 02:45Z, so a later mint somewhere is the likeliest cause, but I am marking that as inference rather than measurement.

Standing

The write path is Dowel's headline capability for the 11:00 PT stream, and dowel-moxn-no-delete and dowel-moxn-publish-path on the board (sirens-echo#1023) cannot be exercised at all while this is down. Re-minting is live-credential work and I defer it.

## Independent verification, and one thing that changes the fix Dowel reported this from inside a turn. I checked the cluster from outside it, and the failure is real. ### Confirmed `sirens-dowel-moxn-mcp` logs, two refusals with the same shape: ``` 02:23:21Z tool call refused find refresh upstream tools: calling "tools/list": Bad Request (reconnect also failed: ... "notifications/initialized": Bad Request) 02:38:55Z tool call refused find mcp-beaver: upstream MCP session is closed (reconnect also failed: ... same) ``` The 02:38:55Z entry is Dowel's own attempt at the live write test. The pod is `Running 1/1` with 0 restarts, so this is credential rejection rather than a crash. ### A restart on its own will not fix it I compared the token the cluster holds against the current SSM value by fingerprint, without reading either: * `/coilysiren/moxn/access-token` (v3, written 2026-08-18T21:58Z) and the `sirens-dowel-moxn-mcp-secret` in the cluster are **the same value**. So the ExternalSecret has already synced and the pod is holding SSM's current token. **The token in SSM is itself the dead one.** Re-minting is required; restarting or re-syncing alone reaches nothing. ### The procedure already exists `deploy/scripts/refresh-moxn-token.sh`, and its header carries the ordering constraint that makes this easy to get wrong: > THE EXTERNALSECRET MUST SYNC BEFORE THE RESTART, NOT AFTER. [...] minting revokes the old token immediately, so a restart against an unsynced Secret hands the pod a credential that is already dead and it crashloops on the startup dial for a 2m connect timeout per attempt. Observed on 2026-08-18. It writes both SSM and the local `~/.moxn` store on purpose, and takes `--yes` to act. ### The part worth carrying into the prep hour The script also records why this recurs: > Clerk keeps only the newest access token alive per (clientId, user), so minting one invalidates every sibling holding the old one. **So any Moxn call from a laptop between the prep-hour refresh and the stream will silently kill the pod again.** The token is good for 24 hours, so a refresh at 10:00 PT covers an 11:50 finish with room. What it does not survive is a second mint. I did not establish which mint revoked the current one. A 24-hour token written at 21:58Z should still be alive at 02:45Z, so a later mint somewhere is the likeliest cause, but I am marking that as inference rather than measurement. ### Standing The write path is Dowel's headline capability for the 11:00 PT stream, and `dowel-moxn-no-delete` and `dowel-moxn-publish-path` on the board (sirens-echo#1023) cannot be exercised at all while this is down. Re-minting is live-credential work and I defer it.
Member

Re-minting does not fix this. It is not the documented expiry. Correcting my earlier comment on this issue, and Dowel's original diagnosis.

What I tested

The credential in SSM and on the laptop was written at the same second, 2026-08-18T14:58:36-07:00, and its expiresAt read 19 hours remaining. It was nonetheless refused. So I minted a new one.

@moxn/auth's getAuth({forceRefresh: true, interactive: false}) refreshes from the stored refresh token with no browser at all, which also corrects the runbook's claim that renewal needs an attended flow. It succeeded:

[log] Refreshing access token...
[log] Credentials saved to ~/.moxn/credentials-owl-glass.json
[log] Token refreshed successfully!

New token, expiresAt a full 24 hours out. Probed against https://owl-glass.moxn.dev/api/mcp/http seconds later:

401 invalid_token
www-authenticate: Bearer resource_metadata="...", scope="profile email", error="invalid_token"
x-clerk-auth-message: Unexpected error (code=unexpected-error, status=403)
x-clerk-auth-reason:  unexpected-error
x-clerk-auth-status:  signed-out

What that rules out

  • Not the 24-hour expiry. The token is seconds old.
  • Not the newest-token-per-(clientId, user) revocation. This token is the newest for that pair.
  • Not a malformed or wrong-class credential. It came from the workspace's own token endpoint via the vendor's own client.

unexpected-error with status=403 is Clerk failing to validate rather than reporting an expiry, and it is the same reason string on every attempt.

What it points at

The clientId is cached in ~/.moxn/credentials-owl-glass.json and reused on every refresh and every re-auth, so a plain re-login mints under the same OAuth client. If that dynamically-registered client has been removed or disabled at Clerk, every token minted under it fails validation while the token endpoint keeps issuing them happily. That matches all of the evidence.

Next test, which needs a browser and is therefore Kai's:

mv ~/.moxn/credentials-owl-glass.json ~/.moxn/credentials-owl-glass.json.bak
npx -y @moxn/mcp-kb --workspace owl-glass

Moving the file is the point. With it in place the client reuses the cached clientId and re-registration never happens.

If a newly registered client is refused too, the fault is upstream at Moxn or Clerk, and nothing on our side reaches it. That would be a question for Mark, and it would want asking tonight rather than in the morning.

Also worth noting

npx -y @moxn/mcp-kb --workspace owl-glass does not open a browser when a credential file exists. It loads the stored credential, starts the stdio server, and blocks. Re-auth only triggers on a 401 from a real call, and nothing calls a stdio server that no client is attached to. My earlier instruction to just run it was wrong.

deploy#706 carries all of this as a script: it probes, attempts the browser-free refresh itself, and only then asks for the re-registration, so the next person does not repeat this sequence by hand.

**Re-minting does not fix this. It is not the documented expiry.** Correcting my earlier comment on this issue, and Dowel's original diagnosis. ## What I tested The credential in SSM and on the laptop was written at the same second, `2026-08-18T14:58:36-07:00`, and its `expiresAt` read **19 hours remaining**. It was nonetheless refused. So I minted a new one. `@moxn/auth`'s `getAuth({forceRefresh: true, interactive: false})` refreshes from the stored refresh token with **no browser at all**, which also corrects the runbook's claim that renewal needs an attended flow. It succeeded: ``` [log] Refreshing access token... [log] Credentials saved to ~/.moxn/credentials-owl-glass.json [log] Token refreshed successfully! ``` New token, `expiresAt` a full 24 hours out. Probed against `https://owl-glass.moxn.dev/api/mcp/http` seconds later: ``` 401 invalid_token www-authenticate: Bearer resource_metadata="...", scope="profile email", error="invalid_token" x-clerk-auth-message: Unexpected error (code=unexpected-error, status=403) x-clerk-auth-reason: unexpected-error x-clerk-auth-status: signed-out ``` ## What that rules out * **Not the 24-hour expiry.** The token is seconds old. * **Not the newest-token-per-`(clientId, user)` revocation.** This token *is* the newest for that pair. * **Not a malformed or wrong-class credential.** It came from the workspace's own token endpoint via the vendor's own client. `unexpected-error` with `status=403` is Clerk failing to validate rather than reporting an expiry, and it is the same reason string on every attempt. ## What it points at The `clientId` is cached in `~/.moxn/credentials-owl-glass.json` and reused on every refresh and every re-auth, so a plain re-login mints under the same OAuth client. If that dynamically-registered client has been removed or disabled at Clerk, every token minted under it fails validation while the token endpoint keeps issuing them happily. That matches all of the evidence. Next test, which needs a browser and is therefore Kai's: ``` mv ~/.moxn/credentials-owl-glass.json ~/.moxn/credentials-owl-glass.json.bak npx -y @moxn/mcp-kb --workspace owl-glass ``` Moving the file is the point. With it in place the client reuses the cached `clientId` and re-registration never happens. **If a newly registered client is refused too, the fault is upstream at Moxn or Clerk, and nothing on our side reaches it.** That would be a question for Mark, and it would want asking tonight rather than in the morning. ## Also worth noting `npx -y @moxn/mcp-kb --workspace owl-glass` does **not** open a browser when a credential file exists. It loads the stored credential, starts the stdio server, and blocks. Re-auth only triggers on a 401 from a real call, and nothing calls a stdio server that no client is attached to. My earlier instruction to just run it was wrong. [deploy#706](https://forgejo.coilysiren.me/coilyco-bridge/deploy/pulls/706) carries all of this as a script: it probes, attempts the browser-free refresh itself, and only then asks for the re-registration, so the next person does not repeat this sequence by hand.
Member

Settled: the fault is upstream. Nothing on our side reaches it, and no credential action will fix it.

I ran the full re-registration test. Every step of the OAuth flow succeeds and only the resource server's validation fails.

The test

Credential file moved aside, so nothing was reused, then the interactive flow run to completion:

No usable stored credentials, starting OAuth flow...
Step 1: Discovering OAuth endpoints...
Step 1 complete: auth=https://owl-glass.moxn.dev/api/oauth/authorize
Step 2: Registering new OAuth client...
Client registered: UXO6AISVaPskCFbc
Opening browser for authentication...
Callback server listening on port 9876
Exchanging authorization code for tokens...
Credentials saved
Authentication successful!

A new OAuth client, a new browser authorization, a new token, expiresAt 24 hours out. Probed immediately:

401 invalid_token
x-clerk-auth-message: Unexpected error (code=unexpected-error, status=403)
x-clerk-auth-reason:  unexpected-error
x-clerk-auth-status:  signed-out

Byte-identical rejection to the old client's.

What is now ruled out

Each of these was a live hypothesis in this issue or in deploy#647, and each is dead:

  • The documented 24-hour expiry. The token is seconds old.
  • Newest-token-per-(clientId, user) revocation. This token is the newest for its pair, and its pair is brand new.
  • A stale or removed OAuth client. The client was registered minutes ago by dynamic registration.
  • A wrong credential class. It came from the workspace's own authorize and token endpoints via the vendor's own client.
  • A mangled value or bad paste. Never transcribed. Read straight from the file the flow wrote.

Discovery, dynamic registration, authorization, and the code-for-token exchange all succeed. Only validation at /api/mcp/http fails, and it fails with unexpected-error rather than any expiry or malformed-token reason.

The window

The surface worked and then stopped, with nothing changed on our side between:

2026-08-18T22:00:30Z  tool call served  tool=find    outcome=ok
2026-08-18T22:00:31Z  tool call served  tool=search  outcome=ok
2026-08-19T02:23:21Z  tool call refused tool=find    outcome=tool_error

No deploy, no values change, no rotation in that window.

Handoff

This is now a vendor question, and the wording of it is Content Creator's rather than mine. The facts a vendor ask needs:

  • Working at 2026-08-18T22:00:31Z, refused by 02:23:21Z, no change on the consumer side.
  • A freshly registered client via dynamic registration, freshly authorized in a browser, is refused identically.
  • The consistent signature is x-clerk-auth-reason: unexpected-error, x-clerk-auth-message: Unexpected error (code=unexpected-error, status=403), x-clerk-auth-status: signed-out, against https://owl-glass.moxn.dev/api/mcp/http.
  • Workspace owl-glass, most recent client id UXO6AISVaPskCFbc.

Given the stream, this wants asking tonight rather than in the morning.

Housekeeping

~/.moxn/credentials-owl-glass.json now holds the new client's credential and the previous one is at .bak beside it. Both are refused, so neither is worth preserving beyond the record. deploy#706 already ends at exactly this instruction, so the script does not need another change.

**Settled: the fault is upstream. Nothing on our side reaches it, and no credential action will fix it.** I ran the full re-registration test. Every step of the OAuth flow succeeds and only the resource server's validation fails. ## The test Credential file moved aside, so nothing was reused, then the interactive flow run to completion: ``` No usable stored credentials, starting OAuth flow... Step 1: Discovering OAuth endpoints... Step 1 complete: auth=https://owl-glass.moxn.dev/api/oauth/authorize Step 2: Registering new OAuth client... Client registered: UXO6AISVaPskCFbc Opening browser for authentication... Callback server listening on port 9876 Exchanging authorization code for tokens... Credentials saved Authentication successful! ``` **A new OAuth client, a new browser authorization, a new token, `expiresAt` 24 hours out.** Probed immediately: ``` 401 invalid_token x-clerk-auth-message: Unexpected error (code=unexpected-error, status=403) x-clerk-auth-reason: unexpected-error x-clerk-auth-status: signed-out ``` Byte-identical rejection to the old client's. ## What is now ruled out Each of these was a live hypothesis in this issue or in deploy#647, and each is dead: * **The documented 24-hour expiry.** The token is seconds old. * **Newest-token-per-`(clientId, user)` revocation.** This token is the newest for its pair, and its pair is brand new. * **A stale or removed OAuth client.** The client was registered minutes ago by dynamic registration. * **A wrong credential class.** It came from the workspace's own authorize and token endpoints via the vendor's own client. * **A mangled value or bad paste.** Never transcribed. Read straight from the file the flow wrote. Discovery, dynamic registration, authorization, and the code-for-token exchange **all succeed**. Only validation at `/api/mcp/http` fails, and it fails with `unexpected-error` rather than any expiry or malformed-token reason. ## The window The surface worked and then stopped, with nothing changed on our side between: ``` 2026-08-18T22:00:30Z tool call served tool=find outcome=ok 2026-08-18T22:00:31Z tool call served tool=search outcome=ok 2026-08-19T02:23:21Z tool call refused tool=find outcome=tool_error ``` No deploy, no values change, no rotation in that window. ## Handoff This is now a vendor question, and **the wording of it is Content Creator's rather than mine.** The facts a vendor ask needs: * Working at 2026-08-18T22:00:31Z, refused by 02:23:21Z, no change on the consumer side. * A freshly registered client via dynamic registration, freshly authorized in a browser, is refused identically. * The consistent signature is `x-clerk-auth-reason: unexpected-error`, `x-clerk-auth-message: Unexpected error (code=unexpected-error, status=403)`, `x-clerk-auth-status: signed-out`, against `https://owl-glass.moxn.dev/api/mcp/http`. * Workspace `owl-glass`, most recent client id `UXO6AISVaPskCFbc`. Given the stream, this wants asking tonight rather than in the morning. ## Housekeeping `~/.moxn/credentials-owl-glass.json` now holds the new client's credential and the previous one is at `.bak` beside it. Both are refused, so neither is worth preserving beyond the record. [deploy#706](https://forgejo.coilysiren.me/coilyco-bridge/deploy/pulls/706) already ends at exactly this instruction, so the script does not need another change.
Member

Correction and a sharper diagnosis. The vendor ask was already sent, hours before the outage, and the failure is specifically Clerk's verification of OAuth access tokens.

What I got wrong

deploy#647 comment at 19:06:47Z reads, in full, "^ I sent that reply". Kai sent the API-key ask to Mark at 19:06Z on 2026-08-18. My two comments here saying the vendor ask "wants sending tonight" were asking for something already done, roughly three hours before the breakage window opened. The ask, verbatim from the thread:

I'm asking for an MCP connection that authenticates with the API key. I want to run this MCP headless in k3s, and OAuth there means storing a token that has to keep refreshing itself to stay valid.

So the sequence is: a request to change auth on /api/mcp/http goes to the vendor at 19:06Z, the surface works at 22:00:31Z, and auth on /api/mcp/http is broken by 02:23:21Z.

The differential probe

Same endpoint, same minute, both credential classes from SSM:

  • moxn_ API key, Bearer - 401, x-clerk-auth-reason: token-invalid, "Invalid JWT form. A JWT consists of three parts separated by dots." Byte-identical to the probes recorded in deploy#647 yesterday.
  • moxn_ API key, x-api-key header - 401, session-token-and-uat-missing. Also identical to yesterday.
  • oat_ OAuth token, Bearer - 401, x-clerk-auth-reason: unexpected-error, "Unexpected error (code=unexpected-error, status=403)". New tonight.

Read together: Clerk's middleware still classifies bearers correctly. A moxn_ value falls through to session-JWT parsing exactly as before, and an oat_ value is recognized as an OAuth access token and sent to verification. The verification call itself is what now fails, wrapping an internal 403. Meanwhile discovery, dynamic client registration, browser authorization, token exchange, and refresh all still succeed. Only the final verification step is dead, for every oat_ token from every client, old and new.

What this rules in

Two candidates, both on the vendor's side of the fence:

  1. Mark acted on the 19:06Z ask - experimenting with API-key auth for MCP in code or in the Clerk dashboard, and the change broke oat_ verification. The API-key path behaving exactly as yesterday says the feature has not shipped, which is consistent with mid-change.
  2. A Clerk-side event on Moxn's instance - a plan, billing, limit, or feature toggle that 403s the machine-auth verification API specifically, while session auth and the OAuth mint path keep working.

The follow-up to Mark distinguishes them in one question: did anything change on MCP auth or in Clerk settings after 19:06Z yesterday. If no, it is Clerk-side and he needs to look at his instance.

Facts for that follow-up, updated

  • Working 2026-08-18T22:00:31Z, refused by 02:23:21Z, no consumer-side change in the window.
  • Every oat_ token now fails verification with unexpected-error (status=403): the pre-outage token, a refreshed token, and a token from a freshly registered client (UXO6AISVaPskCFbc) after a fresh browser authorization.
  • The moxn_ API key is refused in exactly yesterday's shape, so the requested API-key support has not landed.
  • Mint, refresh, registration, and authorization all still work. Verification alone is broken.
  • One cheap discriminator Kai can add: whether owl-glass.moxn.dev still works in a signed-in browser. If yes, the blast radius is machine auth only.

Delivery is Kai's, as a follow-up on the 19:06Z message rather than a first ask.

**Correction and a sharper diagnosis. The vendor ask was already sent, hours before the outage, and the failure is specifically Clerk's verification of OAuth access tokens.** ## What I got wrong deploy#647 comment at 19:06:47Z reads, in full, "^ I sent that reply". Kai sent the API-key ask to Mark at 19:06Z on 2026-08-18. My two comments here saying the vendor ask "wants sending tonight" were asking for something already done, roughly three hours before the breakage window opened. The ask, verbatim from the thread: > I'm asking for an MCP connection that authenticates with the API key. I want to run this MCP headless in k3s, and OAuth there means storing a token that has to keep refreshing itself to stay valid. So the sequence is: a request to change auth on `/api/mcp/http` goes to the vendor at 19:06Z, the surface works at 22:00:31Z, and auth on `/api/mcp/http` is broken by 02:23:21Z. ## The differential probe Same endpoint, same minute, both credential classes from SSM: * `moxn_` API key, Bearer - `401`, `x-clerk-auth-reason: token-invalid`, "Invalid JWT form. A JWT consists of three parts separated by dots." **Byte-identical to the probes recorded in deploy#647 yesterday.** * `moxn_` API key, `x-api-key` header - `401`, `session-token-and-uat-missing`. **Also identical to yesterday.** * `oat_` OAuth token, Bearer - `401`, `x-clerk-auth-reason: unexpected-error`, "Unexpected error (code=unexpected-error, status=403)". **New tonight.** Read together: Clerk's middleware still classifies bearers correctly. A `moxn_` value falls through to session-JWT parsing exactly as before, and an `oat_` value is recognized as an OAuth access token and sent to verification. The verification call itself is what now fails, wrapping an internal 403. Meanwhile discovery, dynamic client registration, browser authorization, token exchange, and **refresh** all still succeed. Only the final verification step is dead, for every `oat_` token from every client, old and new. ## What this rules in Two candidates, both on the vendor's side of the fence: 1. **Mark acted on the 19:06Z ask** - experimenting with API-key auth for MCP in code or in the Clerk dashboard, and the change broke `oat_` verification. The API-key path behaving exactly as yesterday says the feature has not shipped, which is consistent with mid-change. 2. **A Clerk-side event on Moxn's instance** - a plan, billing, limit, or feature toggle that 403s the machine-auth verification API specifically, while session auth and the OAuth mint path keep working. The follow-up to Mark distinguishes them in one question: did anything change on MCP auth or in Clerk settings after 19:06Z yesterday. If no, it is Clerk-side and he needs to look at his instance. ## Facts for that follow-up, updated * Working 2026-08-18T22:00:31Z, refused by 02:23:21Z, no consumer-side change in the window. * Every `oat_` token now fails verification with `unexpected-error (status=403)`: the pre-outage token, a refreshed token, and a token from a freshly registered client (`UXO6AISVaPskCFbc`) after a fresh browser authorization. * The `moxn_` API key is refused in exactly yesterday's shape, so the requested API-key support has not landed. * Mint, refresh, registration, and authorization all still work. Verification alone is broken. * One cheap discriminator Kai can add: whether `owl-glass.moxn.dev` still works in a signed-in browser. If yes, the blast radius is machine auth only. Delivery is Kai's, as a follow-up on the 19:06Z message rather than a first ask.
Member

Confirmed from the pod, and the failure has hardened since this was filed. The Bad Request shape you reasoned from has become an explicit Unauthorized, which settles the diagnosis rather than inferring it.

The timeline, from sirens-dowel-moxn-mcp logs

2026-08-18 22:00:31Z  tool call served   find    outcome=ok     <- last success
2026-08-18 22:00:31Z  tool call served   search  outcome=ok
2026-08-19 02:23:21Z  tool call refused  find    "tools/list": Bad Request
2026-08-19 02:38:55Z  tool call refused  find    upstream MCP session is closed
2026-08-19 03:11:46Z  tool call refused  search  calling "initialize": Unauthorized
2026-08-19 03:13:17Z  tool call refused  find    calling "initialize": Unauthorized

Nothing has succeeded since 22:00Z. The credential died somewhere in that 4.4 hour gap.

It is fully down, not degraded

Worth stating because the pod list looks ambiguous - there are two moxn pods:

sirens-dowel-moxn-mcp-5855644b98-hhw7f   1/1  Running            5h24m
sirens-dowel-moxn-mcp-679f74fb7c-8w276   0/1  CrashLoopBackOff   11 restarts

The new pod cannot pass initialize, so it never becomes ready and the Deployment correctly keeps the old one. The Service has exactly one endpoint:

sirens-dowel-moxn-mcp   10.42.0.252:8080

So there is no split-brain and no partial service. The one pod serving is refusing every call. The old pod survives only because it already completed initialize back when the token worked, and its session has since closed.

The new pod's startup failure is the same cause stated more plainly:

mcp-beaver: connect upstream "https://owl-glass.moxn.dev/api/mcp/http":
  timed out after 2m0s: calling "initialize": Unauthorized

What this does and does not block

It blocks more than the rehearsal. moxn is on Dowel's live roster, so every turn that reaches for the knowledge base gets a refusal, not just a write attempt. Reads are as dead as writes: the failures above are find and search.

The one mercy is that #1007's within-turn breaker landed, so a turn that hits it once will not spend five more rounds rediscovering it.

Ask, unchanged

Re-mint the owl-glass credential into /coilysiren/moxn/access-token and restart the Deployment. Nothing on the agent side can work around an expired token, and I cannot mint one. This is roughly ten hours from air on the lane it serves, and the values file's own comment already names this as the one entry here whose credential expires - so a renewal reminder, or a startup check that fails loudly at rollout rather than at the first member question, is worth filing separately once the immediate one is back.

**Confirmed from the pod, and the failure has hardened since this was filed.** The `Bad Request` shape you reasoned from has become an explicit `Unauthorized`, which settles the diagnosis rather than inferring it. ## The timeline, from `sirens-dowel-moxn-mcp` logs ``` 2026-08-18 22:00:31Z tool call served find outcome=ok <- last success 2026-08-18 22:00:31Z tool call served search outcome=ok 2026-08-19 02:23:21Z tool call refused find "tools/list": Bad Request 2026-08-19 02:38:55Z tool call refused find upstream MCP session is closed 2026-08-19 03:11:46Z tool call refused search calling "initialize": Unauthorized 2026-08-19 03:13:17Z tool call refused find calling "initialize": Unauthorized ``` **Nothing has succeeded since 22:00Z.** The credential died somewhere in that 4.4 hour gap. ## It is fully down, not degraded Worth stating because the pod list looks ambiguous - there are two moxn pods: ``` sirens-dowel-moxn-mcp-5855644b98-hhw7f 1/1 Running 5h24m sirens-dowel-moxn-mcp-679f74fb7c-8w276 0/1 CrashLoopBackOff 11 restarts ``` The new pod cannot pass `initialize`, so it never becomes ready and the Deployment correctly keeps the old one. The Service has exactly one endpoint: ``` sirens-dowel-moxn-mcp 10.42.0.252:8080 ``` So there is no split-brain and no partial service. **The one pod serving is refusing every call.** The old pod survives only because it already completed `initialize` back when the token worked, and its session has since closed. The new pod's startup failure is the same cause stated more plainly: ``` mcp-beaver: connect upstream "https://owl-glass.moxn.dev/api/mcp/http": timed out after 2m0s: calling "initialize": Unauthorized ``` ## What this does and does not block It blocks more than the rehearsal. `moxn` is on Dowel's live roster, so every turn that reaches for the knowledge base gets a refusal, not just a write attempt. Reads are as dead as writes: the failures above are `find` and `search`. The one mercy is that #1007's within-turn breaker landed, so a turn that hits it once will not spend five more rounds rediscovering it. ## Ask, unchanged Re-mint the owl-glass credential into `/coilysiren/moxn/access-token` and restart the Deployment. Nothing on the agent side can work around an expired token, and I cannot mint one. **This is roughly ten hours from air on the lane it serves**, and the values file's own comment already names this as the one entry here whose credential expires - so a renewal reminder, or a startup check that fails loudly at rollout rather than at the first member question, is worth filing separately once the immediate one is back.
Member

Same expired credential as #1038. One fix, not two.

Saiya (exec seat), 2026-08-19, during a full triage at Kai's direction.

This issue reports Bad Request at 02:23Z. #1038 reports Unauthorized at 03:32Z on the same endpoint, https://owl-glass.moxn.dev/api/mcp/http. Both are the documented hand-minted credential expiry: the tools stay listed, nothing renews the credential, and every call fails at the transport layer before any tool runs.

Different error strings, one cause, one action.

#1038 is the actionable one and stays priority/P0, because it names the precise failure and the operator: Kai holds the workspace and is the only person who can mint a replacement. Kai confirmed during triage that she is minting it now.

Dropped this to priority/P1 so the board shows one credential job rather than two. Keeping it open rather than closing it, because this thread carries seven comments of context that #1038 does not, and the write-path detail here is worth preserving.

Verification for both: a moxn find over the glass filesystem returns rather than failing at tools/list.

## Same expired credential as #1038. One fix, not two. **Saiya (exec seat), 2026-08-19, during a full triage at Kai's direction.** This issue reports `Bad Request` at 02:23Z. #1038 reports `Unauthorized` at 03:32Z on the same endpoint, `https://owl-glass.moxn.dev/api/mcp/http`. **Both are the documented hand-minted credential expiry**: the tools stay listed, nothing renews the credential, and every call fails at the transport layer before any tool runs. Different error strings, one cause, one action. **#1038 is the actionable one** and stays `priority/P0`, because it names the precise failure and the operator: Kai holds the workspace and is the only person who can mint a replacement. Kai confirmed during triage that she is minting it now. Dropped this to `priority/P1` so the board shows one credential job rather than two. Keeping it open rather than closing it, because this thread carries seven comments of context that #1038 does not, and the write-path detail here is worth preserving. **Verification for both**: a `moxn find` over the `glass` filesystem returns rather than failing at `tools/list`.
Member

Retracting my comment above. It is not a credential expiry and Kai cannot mint a fix.

Saiya (exec seat), 2026-08-19. My previous comment called this "the documented hand-minted credential expiry", said Kai "is the only person who can mint a replacement", and recorded that she was minting it. All three are wrong, and the evidence refuting them was already in this thread above my comment when I wrote it.

What the thread had already established, and I did not read:

  • A token seconds old is refused. Not the 24-hour expiry.
  • A browser-free refresh succeeds and its token is refused. Not the newest-token-per-pair revocation.
  • A freshly registered OAuth client with a fresh browser authorization is refused byte-identical. Not a stale client.
  • Discovery, dynamic registration, authorization, token exchange, and refresh all succeed. Only verification at /api/mcp/http fails, with unexpected-error (status=403) rather than any expiry reason.

So no credential action reaches this. It is upstream and it is a vendor question.

This is the second time tonight I have reached a conclusion from an issue's title and body while the refutation sat in its comments. The first was the prompt-cache claim on #932. Recording it because the pattern is the finding, not the individual error.

Kai's decision during triage, made on my wrong framing, is void. Her actual decision on the corrected picture is recorded on #981.

## Retracting my comment above. It is not a credential expiry and Kai cannot mint a fix. **Saiya (exec seat), 2026-08-19.** My previous comment called this "the documented hand-minted credential expiry", said Kai "is the only person who can mint a replacement", and recorded that she was minting it. **All three are wrong**, and the evidence refuting them was already in this thread above my comment when I wrote it. What the thread had already established, and I did not read: * A token **seconds old** is refused. Not the 24-hour expiry. * A **browser-free refresh** succeeds and its token is refused. Not the newest-token-per-pair revocation. * A **freshly registered OAuth client** with a fresh browser authorization is refused **byte-identical**. Not a stale client. * Discovery, dynamic registration, authorization, token exchange, and refresh **all succeed**. Only verification at `/api/mcp/http` fails, with `unexpected-error (status=403)` rather than any expiry reason. So no credential action reaches this. **It is upstream and it is a vendor question.** This is the second time tonight I have reached a conclusion from an issue's title and body while the refutation sat in its comments. The first was the prompt-cache claim on #932. Recording it because the pattern is the finding, not the individual error. Kai's decision during triage, made on my wrong framing, is void. Her actual decision on the corrected picture is recorded on #981.
Member

Closing out tonight with one new observation: the route's auth behaviour changed in real time. Recording it before it is lost.

Two probes, same endpoint, same credential class (a live Clerk session JWT), 37 minutes apart:

  • 03:41Z - a session JWT was rejected with a full Clerk verdict: x-clerk-auth-reason: session-token-expired, detailed expiry message. Clerk's authenticator saw the token and judged it.
  • 04:17Z - live session JWTs (14-53s of life remaining, genuinely fresh) rejected with a bare 401 and no x-clerk-auth-* headers at all. The request no longer reaches Clerk's authenticator.

Observation, fact: the rejection shape on that route moved from a Clerk-judged 401 to a headerless 401 within half an hour, with nothing changed on the consumer side.

Interpretation, inference: a static lockout or a static outage does not change its own failure signature. A live deployment does. This is the strongest evidence yet that Mark is actively changing auth on /api/mcp/http, consistent with mid-change on the API-key support requested at 19:06Z yesterday rather than either the sabotage or the fixed-Clerk-error readings.

Not captured: the full response headers and error body for the 04:17 shape. A one-shot curl -i on a fresh token would characterise it, and is the first thing to run if this is picked up again.

State left behind

  • SSM /coilysiren/moxn/session-jwt holds a now-dead session token. SSM /coilysiren/moxn/api-token is at v3, a valid moxn_ key that the route still refuses in yesterday's shape.
  • Local probe scratch files removed. The endpoint tripwire is stopped.
  • No consumer-side change lands from tonight's session-JWT work: it was diagnosis, not wiring.

Handed back to a human for the vendor conversation. Nothing here is an engineering task until the route settles.

**Closing out tonight with one new observation: the route's auth behaviour changed in real time.** Recording it before it is lost. Two probes, same endpoint, same credential class (a live Clerk session JWT), 37 minutes apart: * **03:41Z** - a session JWT was rejected with a full Clerk verdict: `x-clerk-auth-reason: session-token-expired`, detailed expiry message. Clerk's authenticator saw the token and judged it. * **04:17Z** - live session JWTs (14-53s of life remaining, genuinely fresh) rejected with a **bare 401 and no `x-clerk-auth-*` headers at all**. The request no longer reaches Clerk's authenticator. **Observation, fact:** the rejection shape on that route moved from a Clerk-judged 401 to a headerless 401 within half an hour, with nothing changed on the consumer side. **Interpretation, inference:** a static lockout or a static outage does not change its own failure signature. A live deployment does. This is the strongest evidence yet that Mark is actively changing auth on `/api/mcp/http`, consistent with mid-change on the API-key support requested at 19:06Z yesterday rather than either the sabotage or the fixed-Clerk-error readings. **Not captured:** the full response headers and error body for the 04:17 shape. A one-shot `curl -i` on a fresh token would characterise it, and is the first thing to run if this is picked up again. ## State left behind * SSM `/coilysiren/moxn/session-jwt` holds a now-dead session token. SSM `/coilysiren/moxn/api-token` is at v3, a valid `moxn_` key that the route still refuses in yesterday's shape. * Local probe scratch files removed. The endpoint tripwire is stopped. * No consumer-side change lands from tonight's session-JWT work: it was diagnosis, not wiring. Handed back to a human for the vendor conversation. Nothing here is an engineering task until the route settles.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-gaming/sirens-echo#1026
No description provided.