The Steam client/PICS plane cannot open its websocket, and get_account_licenses hangs unbounded instead of failing #21

Closed
opened 2026-08-19 22:46:26 +00:00 by coilyco-ops · 1 comment
Member

Filed by Olaf (ops seat, claude) from a live status check of the deployed steam-mcp, 2026-08-19 ~22:41-22:49 UTC.

What is healthy

Everything except the client/PICS plane. Pod steam-mcp-ff94577f-2mxkv on kai-server, image forgejo.coilysiren.me/coilyco-gaming/steam-mcp:3a0cacf26c3c4d7bc70c5da3c7f51c2debf52183, up 2d1h, 0 restarts. steam-mcp-meta and steam-mcp-oauth2-proxy both Ready. The gated public path answers correctly: /.well-known/oauth-protected-resource returns 200 unauthenticated and /mcp returns 401 without a Bearer JWT. The tailnet NodePort http://kai-server:30112/mcp serves.

Two planes verified live through the MCP:

  • web_api - get_recently_played returned 6 normalized records.
  • storefront - get_store_search_results(query="Eco") returned 10 items with appids and prices. This is the surface coilyco-gaming/sirens-echo#902 reported as returning 0 for every query on 2026-08-17. It works now on the currently deployed image, so #902 looks resolved and wants a close with this as the evidence.

The defect

get_account_licenses (the authenticated client/PICS plane) never returns. I let it run past 8 minutes with no result and no error before stopping it. There is no timeout: the call hangs rather than failing.

Pod stderr at the moment of the call:

22:42:46  Attempting to create a websocket connection to cmp1-iad1.steamserver.net:443 (load: 11.059566)
22:42:47  Websocket closed, cannot reconnect.
22:42:47  Websocket closed, cannot reconnect.

The websocket dies about one second after the attempt, and the underlying client reports it cannot reconnect. The tool call above it never learns that and waits forever.

What the log volume establishes

Aggregated over the pod's entire 2-day life, k8s.deployment.name = 'steam-mcp':

  • body CONTAINS 'websocket' - 3 rows total, all inside the hour of my call.
  • body CONTAINS 'Websocket closed' - 2 rows total, same hour.

So the client session is established lazily on first use, and nobody had exercised this plane in the pod's whole lifetime until now. This is not a regression that started today; it is a plane that has been silently unexercised and is broken on first touch. The web_api and storefront planes carry all observed traffic, which is why nothing surfaced it.

Two separate problems, and the second is the worse one

  1. The session will not establish. Most likely /steam/client-refresh-token is expired or invalid. deploy/main.yml:140 maps it to STEAM_CLIENT_REFRESH_TOKEN; the source reads it and is supposed to persist a rotated token back to the same SSM param. I have not read the token and am not going to. Whether it is expired, whether the rotation write-back is landing, and whether the reconnect gives up too early are all things the source repo can settle and I cannot from outside.

  2. A dead credential presents as a hang, not an error. This is the part that outlives the specific token. A caller gets no result, no exception, and no signal to fall back on. An agent harness with a tool timeout burns its budget; one without it wedges. A failed authenticated read must return an error naming the plane and the cause, and it must do so in seconds. This is the same shape as steam-ops#18's fourth invariant - an unreadable dataset reports null, never zero - extended one step: an unreachable plane reports an error, never silence.

Acceptance

  • get_account_licenses either returns licenses or fails with a named error, in bounded time, on a dead or missing client credential.
  • The websocket path has a connect timeout and a bounded retry, and both are logged with enough detail to tell "credential rejected" from "cannot reach steamserver.net".
  • Every tool on the client/PICS plane gets the same treatment, get_pics_product_info included, since it shares the session.
  • The credential itself is re-seeded or its rotation write-back is confirmed working. That half is an attended operator step per the deploy README's Steam Guard fallback, so it does not land headless.

Not in scope here

The web_api and storefront planes are fine and need no change. Nothing about the Authelia gate or the NodePort is implicated.

Filed by Olaf (ops seat, `claude`) from a live status check of the deployed `steam-mcp`, 2026-08-19 ~22:41-22:49 UTC. ## What is healthy Everything except the client/PICS plane. Pod `steam-mcp-ff94577f-2mxkv` on kai-server, image `forgejo.coilysiren.me/coilyco-gaming/steam-mcp:3a0cacf26c3c4d7bc70c5da3c7f51c2debf52183`, up 2d1h, 0 restarts. `steam-mcp-meta` and `steam-mcp-oauth2-proxy` both Ready. The gated public path answers correctly: `/.well-known/oauth-protected-resource` returns 200 unauthenticated and `/mcp` returns 401 without a Bearer JWT. The tailnet NodePort `http://kai-server:30112/mcp` serves. Two planes verified live through the MCP: * **web_api** - `get_recently_played` returned 6 normalized records. * **storefront** - `get_store_search_results(query="Eco")` returned 10 items with appids and prices. This is the surface coilyco-gaming/sirens-echo#902 reported as returning 0 for every query on 2026-08-17. It works now on the currently deployed image, so #902 looks resolved and wants a close with this as the evidence. ## The defect `get_account_licenses` (the authenticated client/PICS plane) **never returns**. I let it run past 8 minutes with no result and no error before stopping it. There is no timeout: the call hangs rather than failing. Pod stderr at the moment of the call: ``` 22:42:46 Attempting to create a websocket connection to cmp1-iad1.steamserver.net:443 (load: 11.059566) 22:42:47 Websocket closed, cannot reconnect. 22:42:47 Websocket closed, cannot reconnect. ``` The websocket dies about one second after the attempt, and the underlying client reports it cannot reconnect. The tool call above it never learns that and waits forever. ## What the log volume establishes Aggregated over the pod's entire 2-day life, `k8s.deployment.name = 'steam-mcp'`: * `body CONTAINS 'websocket'` - **3 rows total**, all inside the hour of my call. * `body CONTAINS 'Websocket closed'` - **2 rows total**, same hour. So the client session is established lazily on first use, and **nobody had exercised this plane in the pod's whole lifetime until now**. This is not a regression that started today; it is a plane that has been silently unexercised and is broken on first touch. The web_api and storefront planes carry all observed traffic, which is why nothing surfaced it. ## Two separate problems, and the second is the worse one 1. **The session will not establish.** Most likely `/steam/client-refresh-token` is expired or invalid. `deploy/main.yml:140` maps it to `STEAM_CLIENT_REFRESH_TOKEN`; the source reads it and is supposed to persist a rotated token back to the same SSM param. I have not read the token and am not going to. Whether it is expired, whether the rotation write-back is landing, and whether the reconnect gives up too early are all things the source repo can settle and I cannot from outside. 2. **A dead credential presents as a hang, not an error.** This is the part that outlives the specific token. A caller gets no result, no exception, and no signal to fall back on. An agent harness with a tool timeout burns its budget; one without it wedges. A failed authenticated read must return an error naming the plane and the cause, and it must do so in seconds. This is the same shape as steam-ops#18's fourth invariant - an unreadable dataset reports null, never zero - extended one step: an unreachable plane reports an error, never silence. ## Acceptance * `get_account_licenses` either returns licenses or fails with a named error, in bounded time, on a dead or missing client credential. * The websocket path has a connect timeout and a bounded retry, and both are logged with enough detail to tell "credential rejected" from "cannot reach steamserver.net". * Every tool on the client/PICS plane gets the same treatment, `get_pics_product_info` included, since it shares the session. * The credential itself is re-seeded or its rotation write-back is confirmed working. That half is an attended operator step per the deploy README's Steam Guard fallback, so it does not land headless. ## Not in scope here The web_api and storefront planes are fine and need no change. Nothing about the Authelia gate or the NodePort is implicated.
Author
Member

Duplicate of #13, which I found after filing this. #13 has the same root cause, is three weeks older, and already carries the right label and priority.

Evidence folded into #13 as a comment, including the one genuinely new finding: the bounded sanitized error #13 records at ~50 seconds no longer fires, and the call now hangs unbounded instead.

Closing here. Work the problem on #13.

**Duplicate of #13**, which I found after filing this. #13 has the same root cause, is three weeks older, and already carries the right label and priority. Evidence folded into #13 as a comment, including the one genuinely new finding: the bounded sanitized error #13 records at ~50 seconds no longer fires, and the call now hangs unbounded instead. Closing here. Work the problem on #13.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
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/steam-ops#21
No description provided.