Replies: 1 comment
|
The mechanism you diagnosed is real, but it belongs to the pre- What replaced it, and it answers both slices you proposed: 1. A question is a pending scoped waterfall, not a session-scoped client table
2. Slice 2 (server-side ping) is implemented
Both changes are contained in 3. If you still see it on a current version, here is what decides itWe cannot reproduce your hop (no SSH/Tailscale path to this host), so the following is what separates the remaining candidates rather than a verdict:
4. Two honest caveats
If this gets you to the failing link, marking it as the answer will help the next person who loses a card on a proxied connection start from the replay contract instead of the composer. |
Uh oh!
There was an error while loading. Please reload this page.
Symptom
When DeepSeek Harness web is reached over a remote hop (SSH local-forward, Tailscale Serve, or any reverse proxy that is not loopback),
ask_user_questioncan leave the turn blocked on the host while the browser composer never shows the question card.Locally (
http://127.0.0.1:3080) the same session usually works. Remotely the session still looks “running”, tool-call / assistant events keep arriving, but the input bar is never replaced by the question UI. The only recovery is to cancel the turn. After cancel, the tool result is:This is not “the tool is missing from the preset”. The model-facing tool ran. The human never got a card to answer.
Environment that reproduces it
npx @deepseek-ai/dsh web) listening on127.0.0.1:3080ssh -L 3080:127.0.0.1:3080 …, orhttps://<magicdns> → 127.0.0.1:3080)--trusted-hostis set so/apiis not 403 (page loads, history loads, live events flow)@deepseek-ai/dsh-tool-ask-user(official Standard / creative-style presets do)ask_user_questionand then sits waiting for a humanIdle time on the hop (tens of seconds is enough on some SSH / Serve paths) makes it much more likely.
Expected
ask_user_question.question/requestedon the mux downlink./api/respondwith the samerpcId; host settles the tool call; the loop continues.A later mux reconnect while the question is still pending should re-show the same card, because the wait is still live on the host.
Actual (remote)
Steps 1–2 happen. Step 3 often does not. The host stays in
userQuestions.ask()until the user cancels. Refreshing the page does not recover the card if the client later wipes the wait (see below). Cancelling the turn is the only way out.Why this is a remote-only footgun
ask_user_questionis not a session-log event. It is a generation-scoped mux frame:dsh-host-apiproxy)registerProvider.ask()mints anrpcId, stores the wait in an in-memory table, and pushes{ type: "question/requested", sessionId, questions }to every connected mux consumer.rpcId).question/resolvedis also mux-only.session.jsonl. History backfill cannot restore a card.So the card’s entire lifetime is: mux frame → client
Session.pending→conversation.composerchain.The browser downlink is a WebSocket (
GET /api/events.muxupgrade), not SSE. Unary RPCs stay on HTTP. A hop that drops idle WebSockets (SSH-L, Tailscale Serve, many reverse proxies) will tear the mux even while HTTP history/prompt still work. That is why the chat can look healthy and the card still disappears.Race (this is the bug)
Official client reconnect order:
question/requested/approval/requested.host.describe+ both streams open) succeeds →onConnected.Session.resync()rebuilds the history window.In current
dsh-client-runtime,resync()does:History does not contain the question. The frames from step 1 were already applied to
pending. Step 3 deletes them. The composer’sselectQuestionthen sees an emptyinteractionslist and keeps the normal input bar.The comments around disconnect already know the right model:
rpcId.Clearing
pendinginresync()(which runs after that replay) inverts the contract. On a stable loopback socket you rarely reconnect mid-wait, so the bug stays hidden. On SSH / Serve you reconnect often, so the card is gone and the tool is still blocked.There is a second aggravating factor: the host WebSocket downlink does not ping. An idle remote hop can close the socket, which forces the reconnect path above while a question is outstanding.
Suggested direction (not a patch request, just the invariant)
Two independent, demoable slices:
Pending-wait lifetime vs reconnect
Treat
question/requested/approval/requestedas mux-generation state:resync()/onConnected(replay already ran).Keep the downlink alive on a hop
Server-side WebSocket ping (e.g. every 15s) on
/api/events.muxand/api/events.host, so an idle SSH / Serve path does not force a reconnect in the first place.Slice 1 is sufficient to make a pending question survive reconnect. Slice 2 reduces how often you hit the path.
Acceptance
ask_user_questionfrom a session opened through SSH-Lor Tailscale Serve; the question card appears in the composer without cancelling the turn.rpcId/ same options).http://127.0.0.1:3080behaviour is unchanged.Notes
@deepseek-ai/dsh-tool-ask-useris per-agent;@deepseek-ai/dsh-client-ui-user-questionsis the host UI and is already in the web-app overlay. This is not “the tool was not unlocked”.--trusted-host403s are a different class of failure (nothing in/apiworks). Here unary/apiworks; only the wait UI is missing.All reactions