Replies: 4 comments
|
Nice repro write-up. We hit the same symptom family on a self-hosted instance (master build behind a reverse proxy), and the missing piece turned out to be the real-time event channel, not the card itself — worth checking before digging into card rendering:
In our case the root cause was the proxy stripping the WebSocket upgrade (Host header rewritten → 403 on the events endpoints, and an authenticating tunnel returning 200 HTML instead of 101). Fixing the proxy made cards appear reliably. On localhost that particular cause should not apply, but the backgrounded-tab WS death + no re-subscribe path is plausibly the same root family. Useful checks:
If it turns out to be the re-subscribe gap, a minimal client-side fix would be re-emitting / re-querying pending |
|
Thanks — this is a great cross-check, and your proxy finding is a valuable data point for self-hosted users (WS upgrade stripped → 403/200-HTML instead of 101): same symptom family, different trigger. On our side we already ran the triage you list, with two results worth sharing:
Practical consequences: F5 forces the reconnect+replay and recovers the pending card (we documented it in the workaround); and we shipped the ask-timeout side as a community plugin since the harness currently accepts no external PRs: https://github.com/Q1hangL/dsh-ask-guard (a Happy to capture and share console output from the next real reproduction — the |
|
Your half-open-connection analysis checks out against master too — I verified it in the source:
Two distinct silent-failure paths converge on the same missing liveness signal:
Both disappear with a liveness mechanism. The minimal fix that needs no protocol change: a client-side frame watchdog — if no frame (any frame) arrives within N seconds, call Your |
|
Patch is up, exactly the client-side frame-watchdog shape you described — branch https://github.com/Q1hangL/deepseek-harness/tree/fix/mux-frame-watchdog What it does: Evidence so far:
On your master instance: cherry-pick |
Uh oh!
There was an error while loading. Please reload this page.
Bug report:
ask_user_questioncard never appears in browser, turn hangs indefinitelyEnvironment
@deepseek-ai/dshv0.1.0-rc.6 (installed via npx)dsh webprofile, Windows 10, Chrome-class browserSummary
When the model calls
ask_user_question, the browser sometimes never shows thequestion card. The chat shows the tool call stuck in its pending state, and the
turn hangs with no timeout — observed for 22.6 minutes and 6.1 hours in two
incidents. The only way out is pressing Stop, which aborts the turn and returns
Error: ask_user_question was aborted before the user answered(ASK_ABORTED).Reproduction
dsh web, open a session, ask the agent something that triggersask_user_question.backgrounded or frozen (Chrome Memory Saver, Windows Modern Standby).
The turn has been hanging the whole time.
ASK_ABORTED(turn/endreason{kind:"aborted", reason:{kind:"user"}}).perfectly (card renders, answering resolves the call).
Root cause analysis
Two stacked issues:
1. Single-push delivery over a connection with no heartbeat.
dsh-host-apiproxyregisters the web user-questions provider and pushes onequestion/requestedframe to every mux queue(
packages/host/apiproxy/src/index.ts, thectx.userQuestions.registerProviderblock). Pending questions are replayed to a client only when it (re)subscribes
(the
events.muxbaseline loop). The mux WebSocket transport has noapplication-level heartbeat on either side: the client
(
packages/client/connection/src/*readWebSocket) relies solely on theWebSocket
closeevent, and the host WS server sends no ping/pong. When theconnection dies half-open (OS sleep, backgrounded/frozen tab, NAT/proxy idle
timeout — no FIN), the pushed frame is lost, the client never sees
close, soit never reconnects, and the replay path is never reached. The page keeps
displaying stale local state and looks healthy.
2.
ask_user_questiondeclares no timeout budget.The tool's README states it declares no
timeout-policybudget, anddsh-tool-call-timeout-policyonly enforces budgets declared by tool plugins.So a lost question blocks the turn forever. (A budget would turn the hang into
a clean
TOOL_TIMEOUTresult after N minutes.)Evidence: the durable session log shows zero events between
tool/calland theabort in both incidents; an independent WebSocket client connected to
/api/events.muxduring a healthy third run receivedquestion/requested/question/resolved answeredwithin 4 seconds, provingthe host broadcast and replay mechanisms themselves are correct.
Suggested fixes
ws.ping()withterminate-on-no-pong (browsers auto-answer protocol pings) or app-level
ping/pong frames, plus a client-side watchdog. A detected half-open
connection then reconnects, and the existing replay-on-subscribe delivers
pending questions automatically.
ask_user_question: declare atimeoutMs(e.g. 5–10 min)on the tool definition; the existing
dsh-tool-call-timeout-policymechanism turns a lost question into a structured
TOOL_TIMEOUTinstead ofan infinite hang.
show a hint ("no response — press F5 to re-sync or Stop") since a page
refresh triggers the replay path.
Workaround for users
replays the pending question and the card appears; or press Stop and answer
in chat.
All reactions