Repository navigation
web_search can hang forever instead of failing: one stalled request wedges the whole turn #11371
Replies: 3 comments
|
Your diagnosis is right and I measured the boundary of it. What I ran, on the installed copy rather than the source tree —
dsh plugin --profile <profile> add github:argszero/cordis-plugin-tool-deadline-guard#v0.2.1with applyTo: all
onExpiry: replace
perTool: { web_search: 30000, web_fetch: 30000 }Two honest limits. First, One defect I found in my own package while measuring it: under Measured against |
|
One correction to what I posted above, and it comes from my own mount rather than from the report. I handed out Measured on the installed artifact — a real
So install the new tag: dsh plugin --profile <profile> add "github:argszero/cordis-plugin-tool-deadline-guard#v0.2.2"The undeclared arm keeps its sentence verbatim, because there it is a fact; Nothing else above changes, and both honest limits still stand: |
|
Thanks — your first comment sharpened the report rather than just confirming it, so I've edited the original post to lead with your point instead of mine. Conceding it directly: this is not a missing-timeout bug. The budget exists and is declared, and the repo says so itself. On your open caveat — it reproduces on the current line, not just rc.2/rc.3. You measured against const auth = await authHeaders(options, request, signal) // :171
throwIfSearchAborted(signal) // :172
recordRequest?.(endpoint)
throwIfSearchAborted(signal) // :174
response = await fetch(endpoint, {
method: 'POST',
redirect: 'error',
...
...signal !== undefined ? { signal } : {}, // :189
})The request receives only the caller's cancellation signal. No deadline is fused in at any phase — not credential resolution, not the request, not the body read. So a stalled endpoint still wedges the turn on alpha.2, and Stop still cannot unwind it. Two things that make the patch smaller than it looks:
On the correction: installing Both of your limits stand and I'm not going to argue them away: Same failure family, different mechanism, in case it's useful for triage: #11486 — a gateway 413 page misclassified as context overflow, routing a request-size failure into a compaction that cannot shrink the payload. Both are cases of the harness receiving a failure it cannot act on and answering with a recovery path that cannot address it. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
web_searchcan hang forever instead of failing — one stalled request wedges the whole turnSummary
When the DeepSeek Search/Fetch API endpoint stalls (or the credential resolver behind it never
settles),
web_search/web_fetchnever return. The turn staysrunningindefinitely, notool/resultevent is ever appended, and even cancelling does not unwind it — only a processrestart clears it.
Cause:
dispatchDeepSeekWebawaitsfetch()directly, and Node'sfetchhas no defaulttimeout. The cooperative tool-call timeout policy in
dsh-tool-call-timeout-policycan abortexec.signal, but aborting a signal cannot settle a promise that never settles — so the stepabove the tool keeps awaiting it forever.
To be precise about where the gap is, since it is easy to misread this as a missing-timeout bug:
the budget is not missing, it is declared and unenforceable.
applyWebSearchToolattachestimeoutMs=searchTimeoutMs, default30_000(packages/web/tool-web/src/index.ts:29and:65), andpackages/web/tool-web/src/search.ts:339-340documents that parameter as "thecooperative tool-call budget (ms) attached as the tool's
ToolDefinition.timeoutMsfor@deepseek-ai/dsh-tool-call-timeout-policyto enforce" (web_fetchis the same shape atfetch.ts:444-445and:498). A budget that can only signal a tool, against afetch()thatnever reads the signal, is the whole defect. Confirmed still present on the current release line:
at
f3803c96(release(dsh): 0.2.2-alpha.2 (#7437)) the request atpackages/web/web-deepseek-api/src/transport.ts:189receives only the caller's signal —...signal !== undefined ? { signal } : {}— with no deadline fused in at any phase.Reproduction
web-deepseek-api.baseURLat any endpoint that accepts the connection and neverresponds (a plain
net.createServer(() => {})is enough).web_search.tool/callevent is appended; notool/resultever follows. The session showsas running for as long as you care to watch. Pressing Stop /
agent.cancel()does not end it.In the wild I hit the same wedge with a misconfigured endpoint: the API answered
HTTP 402 Insufficient Balancequickly, but the turn had already hung, and the leftover callsurvived the whole session.
Proposed fix
One deadline per operation, spanning every phase — credential resolution, the request, and the
response body read — fused with the caller's cancellation via the existing
@deepseek-ai/dsh-timeoutlibrary. This is the same patterndsh-web-fetch-httpalready usesfor its own transport.
timeoutMssetting on@deepseek-ai/dsh-web-deepseek-api, default 30 000 ms,resolved per operation through
DeepSeekWebAuth.resolveTimeoutMs, validated against Node'stimer range.
distinct error,
WEB_PROVIDER_TIMEOUT.WEB_ABORTED— nobody cancelled it;WEB_PROVIDER_ERROR— the endpoint isn't misconfigured, it was slow.The distinction matters because the model should retry or escalate, not loop on a "config"
error.
WEB_ABORTED.Minimal shape of the change in
packages/web/web-deepseek-api/src/transport.ts:Patch
Available on a fork branch (8 files, +177/−14):
akshaytp1571/deepseek-harness→fix/web-search-hang(commit203ae5e3)Changed:
transport.ts,index.ts(Config + wiring),README.md/README.zh.md,regenerated config catalog, and
tests/transport.spec.ts.Tests
Six new cases in
packages/web/web-deepseek-api/tests/transport.spec.ts:WEB_PROVIDER_TIMEOUTinstead of hangingfetchand is abortedtimeoutMsis rejected before dispatchWEB_ABORTEDwhen it wins the racePackage suite: 132 passed.
gen-config-catalog,verify-translation-pairing,verify-md-wrap,verify-md-links,duplication— clean.Scope
@deepseek-ai/dsh-web-search-deepseek(the alternate Anthropic-native provider) has the sameunbounded-dispatch shape. I left it out of the patch to keep the change reviewable; the same
deadline applies there verbatim if this approach looks right.
Happy to adjust the default (30 s vs. something smaller), the error code name, or move the
deadline into the seam (
dsh-web) so every provider inherits it instead of each one opting in.All reactions