Repository navigation
[bug] Auto review: every remaining tool call returns "aborted before dispatch" after a mid-turn /permission auto switch #10041
Replies: 2 comments
|
Your timeline, your 125-session scan and your code pointers all hold at The gate is the only possible producer — confirmed structurally
But the teardown is supposed to be self-limiting — that is the part that mattersThe disposal hook is not just "abort everything": yield async () => {
accepting = false
try {
for (const session of ctx.sessions.list()) {
if (permissionPresets.current(session) !== AUTO_PRESET) continue
permissionPresets.set(session, 'danger-full-access')
}
} finally {
lifecycle.abort(new Error('auto-review integration disposed'))
await Promise.allSettled([...active])
}
}
So a completed teardown moves every Why halfway is reachable, and why it stays that wayThree facts, one line of source each.
The push order in that effect is That state has exactly one exit: the preset stops resolving to On your three expectations
Minimal fix shape, in the order that matters
Your workaround stands until then, with one correction worth keeping: switching back to a fixed preset is what restores tool calls, so do that first and end the turn afterwards — not the other way round. |
Independent sample: the cancels are not the closed-gate
|
| outcome | n | p50 | min | max |
|---|---|---|---|---|
| normal call | 328 | 1881 ms | 3 ms | — |
aborted before dispatch |
47 | 1377 ms | 0 ms | 4754 ms |
| reviewer failure (schema, synchronous) | — | 2 ms | 2 ms | 2 ms |
46 of 47 aborts took >100 ms, and the median is on the order of a full tool round-trip, not of an early return. A return { kind: 'cancel' } at :692 (!accepting || lifecycle.signal.aborted) is synchronous — it never awaits. Its latency should be indistinguishable from the 2 ms schema-throw, which is the same code path's cost floor.
So in my sample these cancels are coming from :704 or :712 (after the review provider settles), not from the closed gate at entry.
2. Within one step, one call demonstrably passed the gate
In session A, turn 1 step 1, three calls are dispatched sequentially — each tool/call is logged only after the previous tool/result:
call@19:16:26.166 working_directory → result@19:16:27.476 dt=1310ms ABORTED_BEFORE_DISPATCH
call@19:16:27.476 list_agents → result@19:16:28.740 dt=1264ms ABORTED_BEFORE_DISPATCH
call@19:16:28.741 bash → result@19:16:28.743 dt= 2ms Auto review ... schema is missing or ambiguous
The third call is the interesting one. It reached snapshotAutoReview() → nativeAction() and threw there, which means at 19:16:28.741 the listener had already passed both :689 (permissionPresets.current(...) === 'auto') and :692 (accepting === true, lifecycle.signal.aborted === false).
If the gate had been closed — accepting = false with a leaked listener, as the half-torn teardown would leave it — that call would have returned cancel in ~0 ms instead of reaching nativeAction. It did not. The gate was open 1.3 s after two calls in the same step were cancelled.
So either the state flips per call (which a one-shot teardown cannot produce), or those two aborts were not produced by the gate at all.
3. Two other producers render the same text
Error: tool call aborted before dispatch with error.info.code = ABORTED_BEFORE_DISPATCH is minted by toolAbortedBeforeDispatchResult() in packages/core/tools, and it is returned from at least three places, only one of which is the plugin:
prepareExecution, before the waterfall:if (this.callerCancelled(exec))→ the caller's own signal is aborted (~0 ms)prepareExecution, afterawait this.ctx.waterfall(...)returns:if (this.callerCancelled(exec))→ waterfall-duration latency, same text- the waterfall's
decision.kind === 'cancel'→ the auto-review gate
The second one is the exact latency shape I measured, and it does not require the gate to be shut — the review can complete normally (allow, deny, or throw) and the result is then discarded because the caller signal aborted during it.
This matters for attribution: the text alone cannot name the producer, and a scan keyed on ABORTED_BEFORE_DISPATCH will conflate "gate closed" with "caller signal torn down mid-call". Both are real; they have different fixes.
4. What I could not check
Your suggested decisive artifact — a disposal error logged between the last healthy call and the first cancel — is not obtainable on this build from the session log, and there is no host log to grep either: %APPDATA%\@deepseek-ai\dsh-desktop\logs\ contains only crash-*.log, newest dated before the incident. The disposal rejection you expect never reaches any persisted file. So that check needs either a stderr-to-file switch or a host log; I can re-run a repro if one is provided.
I'm not claiming my sample is your sample. Yours is a mid-turn /permission auto switch; mine has no preset switch anywhere near the storm (the preset sat at auto from 19:16:09, the first cancel is 19:16:27, and the next preset event is 19:20:06). That difference is itself worth noting: it suggests more than one route into the same abort storm, and only one of them goes through a preset write.
5. Consequence for the proposed fix
- "When the gate is closed, return
denywith a reason instead ofcancel" — agreed, and it removes the worst of the four outcomes. But it would not have stopped the storm I measured, because the gate there was open. - "Yield the session sweep first so reverse-order disposal runs it last" — agreed, and it is the right shape regardless; it just isn't sufficient for a storm whose cancels originate after a completed review.
- Worth adding: make the abort attributable.
{ kind: 'cancel' }carries noinfo, so three different producers collapse into one string. A code on the decision (gate-closed vs caller-aborted vs post-review-aborted) would make the next report diagnosable from the log alone without a disposal trace.
If the caller-signal variant is also known upstream, ignore §3; the measurement in §1–2 is the part I could not find in the thread.
Uh oh!
There was an error while loading. Please reload this page.
Environment
0.2.1-rc.2(installed 2026-10-17,app.asarpackage versions0.2.1-rc.2)@deepseek-ai/dsh-experimental-auto-reviewmounted (profiles/desktop/cordis.yml, idauto-review)deepseek-official/deepseek-flashdanger-full-access, approvalaskSummary
With the experimental Auto review integration mounted, switching the permission preset to
autoin the middle of a running turn put the Auto-review gate into its cancelled state. Every subsequent tool call for the rest of that turn ended as:The tool bodies never executed, and the turn still ended normally (
completed). Switching back toworkspace-writeand starting a new turn restored normal tool execution. The model/user gets no diagnostic explaining that Auto review is inactive or why the calls were cancelled.Observed (session
session-475143e5-…, desktop profile, 2026-10-18, local time Asia/Shanghai)Timeline from the session log (format v4,
session.v4.jsonl.zstd):approval/asked— apwshcall requested sandbox escalation todanger-full-access; approval pendingcommand/run permission " auto"→permission/preset:auto+sandbox/mode:danger-full-access, command successapproval/decided: allowed-once; the pendingpwshcall completes normallyuser/messageshows the newdanger-full-accesspolicywrite,write,ops_bash,ops_bash,ops_bash,read,ops_bash), steps 98–104, allisError: true,error.code: "ABORTED_BEFORE_DISPATCH", textError: tool call aborted before dispatchturn/end: {"kind":"completed"}— the turn was not aborted/permission workspace-write→ preset + sandbox eventsObserved behavior: the switch to
autowas accepted; the already-approval-pending tool call still ran; but from the next tool call on, every call in that turn was cancelled before dispatch. Noapproval/askedevents were created for those cancelled calls.Second occurrence (session
session-29ea28b9-…, same desktop profile, 2026-10-17)Auto preset was selected before the turn (
/permission auto, 14:16:04). During turn 1,ops_bashat step 12 returnedABORTED_BEFORE_DISPATCHat 14:18:35.877 (seq 113). The immediately following events show a mid-turn tool-registry/plugin reconfiguration:developer/messagewith{"type":"tool-removal","toolName":"ops_bash"}, {"type":"tool-removal","toolName":"pwsh"}writeresultError: cannot get required service "sessionProjections" in inactive contextLater tool calls in that turn and in turns 2–3 worked again, i.e. the gate recovered once the plugin scope settled.
Evidence that this is Auto-specific
Local scan of 125 v4 session logs on this machine: only 2 contain
ABORTED_BEFORE_DISPATCHresults, and in both the effective preset at the time waspermission/preset:auto(7 events in the session above, 1 in the second one). No such events exist underread-only,workspace-writeordanger-full-accesspresets.Why it happens (code pointers, master)
packages/core/tools/src/index.ts— when atools/pre-executewaterfall returns{ kind: 'cancel' }, preparation maps it totoolAbortedBeforeDispatchResult():content: "Error: tool call aborted before dispatch",error.info.code: "ABORTED_BEFORE_DISPATCH", and the tool body is never invoked.packages/experimental/auto-review/src/index.ts— the prependedtools/pre-executelistener is the only producer of{ kind: 'cancel' }for this path. It returnscancelwhen:!accepting || lifecycle.signal.abortedbefore starting a review (src/index.ts:692), andisAborted(lifecycle.signal)after the review provider settles (:704,:712)..agents/notes/implemented/feature/2026-08-28-auto-review.zh.mddocuments this as intended: “Provider 结算后,lifecycle abort 始终形成规范的 dispatch 前取消,包括晚到 allow、deny 或 failure。”lifecyclecontroller is aborted only in the integration teardown (src/index.ts:733):lifecycle.abort(new Error('auto-review integration disposed')).So once the integration's lifecycle has aborted (integration unloaded/reconfigured mid-turn), new tool calls that have no review in flight are still cancelled pre-dispatch by the listener. They are not routed back to the ordinary approval chain, and no user-visible reason is recorded.
Expected
ask), orAuto review is unavailable; approve this call manually/ a dedicated error code, instead of a generic pre-dispatch abort.autoAdmitpresent whileaccepting/lifecycleare already in closing state).Repro (best effort)
pwsh/ops_bashcall withsandbox_permissions: danger-full-access); leave the approval dialog pending./permission auto(or pick theEXPAuto entry in the permission picker) from the same session.Error: tool call aborted before dispatch(ABORTED_BEFORE_DISPATCH); the turn endscompleted;/permission workspace-write+ a new turn restores normal behavior.Suggested workaround (user side)
Do not switch permission presets mid-turn; switch between turns. If the abort storm happens: switch back to
workspace-write(ordanger-full-access), end the turn, and start a new turn.Data availability
Redacted session log excerpts (v4 JSONL) and the two aborted call ids are available on request; no tool bodies executed and no files were modified by the cancelled calls.
Reported on 2026-10-18. DSH Desktop 0.2.1-rc.2 is the latest release at the time of reporting (master
d1b9c04067 release(dsh): 0.2.1-rc.2).All reactions