Fix ask_user_question in DeepSeek Harness goal rounds

TroubleshootingPublished 2026-09-12Author: DeepSeek Plugin Market
DeepSeek HarnessDSH plugingoal roundask_user_questionagent spin
In an autonomous DeepSeek Harness goal round the model calls ask_user_question, you answer, and nobody receives it, so the agent spins for dozens of rounds.

During an autonomous goal round, when the model calls ask_user_question it does not receive your answer but an empty-looking return value, so it keeps spinning for dozens of rounds. The cause is two independent facts combining: goal rounds share the same runtime root as the user (the existing interaction guard only covers subagents, not autonomous rounds), and in Code Mode a nested tool answer only re-enters model context if the program logs or returns it. The right fix is an agent-scoped ctx.tools.guard() that denies ask_user_question for the lifetime of that round so the model records the need through update_goal(blocked); the upstream in-tree fix is ready and waiting for someone to open the pull request, with a behaviour-equivalent community plugin available in the meantime.

DeepSeek Harness goal-round spin as it happens

The symptom reads like "the model ignores the human", but it is really "the model never received the human". The recorded incident:

  1. Nested ask plus two answers, neither taking effect: during an autonomous goal run (Code Mode / run_code, same-session goal with a 256-round cap) the model called, inside run_code, await tools.ask_user_question({...}); return "asked";. The user answered twice, and both answers do appear in the session log as inner tool/code-dispatch results, but the outer tool/result the model sees was only "asked" both times (#6074).
  2. Then a long tail of spinning: the model went on to run 30+ goal rounds, each executing verifier slices and each ending with "Awaiting your two decisions" — waiting for an answer that would never arrive.
  3. A pending question stalls the driver: a pending question declares no timeout budget, so the driver waits forever. The first answer took about 8.5 minutes to arrive in the recorded case, during which the whole autonomous flow was stalled.
  4. Both causes carry half the blame: (1) autonomous rounds have no blocking mechanism for interactive asks, and (2) Code Mode's return-value semantics silently drop nested answers. Fixing only one of them only relieves half the problem.
  5. The decisive debugging check: do not judge by "I asked and the model says it is waiting", but align three layers — whether the inner tool/code-dispatch receipt carries the answer, whether the outer tool/result carries it, and whether the program wrote it back into context at all.

DeepSeek Harness mechanism: goal rounds share the user's runtime root

This crack comes from two precise source facts, and neither is "the model disobeying". Layer by layer:

  1. The interaction guard only covers subagents: dsh-user-questions only rejects live subagents (DELEGATED_CALLER), while a goal round runs as the same runtime root, so nothing stops it from asking interactively mid-autonomy.
  2. A pending question has no budget: the question declares no timeout, so the driver waits indefinitely, and "no one will answer" is never translated into "this should be blocked" — the round simply hangs.
  3. Code Mode only re-feeds logs and returns: in Code Mode only the program's own logs and returns re-enter model context; a tool return value nested inside run_code must be written back explicitly by the program, or a successful execution is equivalent to nothing happening.
  4. ctx.tools.guard() is an existing core primitive: packages/core/tools/src/index.ts:704 defines type ToolGuard = (execution) => string | undefined, where returning a string denies that execution; :1100 registers guard(guard: ToolGuard): () => void, whose comment states that it registers after the extensible tools/pre-execute waterfall and that "a plain-context guard applies globally; one registered through agent.ctx applies only to that agent". That is exactly the scoped semantics "deny only during autonomous rounds" needs.
  5. Why not restrict(): tools.restrict() (:1061) also requires a scoped context and :1064 throws "requires a scoped context (agent.ctx)". More importantly, it hides the tool from the menu, whereas a guard denial is a catchable ToolCallError: the model can plainly see that the tool is disabled for the current turn and switch to update_goal(blocked) or keep pushing, which is precisely what an autonomous round needs.
  6. A plugin can mount on the same seam: the agent/inbox/claimed event (packages/core/agent-loop/src/inbox.ts:114, fired when a message is claimed) together with session/event's user/message (the admit path) and turn/end / turn/start lets a plugin reproduce the same semantics without touching driver source.

DSH plugin recipe: deny interactive asks with ctx.tools.guard()

The recipe is: detect the goal window, install an agent-scoped guard, and dispose it when the window closes. The upstream fix and the community plugin share the same primitive. Specifically:

  1. Window detection: identical to goal-round-driver — only source.kind === 'goal' with round > 0 counts as an autonomous round, so a user's legitimate question in a non-goal round is never affected; everything outside the window passes through.
  2. Install site: register an agent-scoped ctx.tools.guard() on agent/created plus agent/inbox/claimed, returning a denial reason for ask_user_question. The in-tree version implements this inside packages/goal/goal-round-driver/ (adding DriverState.askGuardDispose, GOAL_ROUND_ASK_DENIAL, and the installAskGuard / clearAskGuard helpers), and the core of it is short:
ts
/** Deny interactive human questions while an admitted goal round owns the turn. */
const denyAskDuringRound: ToolGuard = execution =>
  execution.name === 'ask_user_question' ? GOAL_ROUND_ASK_DENIAL : undefined
  1. The clear sites must be complete: clearing only on turn/end is not enough — if a claimed goal round is discarded or superseded and the guard is only cleared at turn end, it lingers and denies a legitimate non-goal ask for the remainder of that turn. The full set also includes agent/session-start, agent/inbox/inserted (competing input), agent/inbox/discarded (superseded), and agent/disposed; on a multi-agent host the turn/end close must additionally be routed per agent (via session.id → agent), or agent A's turn end clears agent B's window early.
  2. The denial text must point somewhere: saying only "this round will continue on its own" is not enough. Use the same in-tree GOAL_ROUND_ASK_DENIAL text so the model is directed to record the need with update_goal(blocked) (including a concrete blocked_reason) rather than silently retrying.
  3. Use the community plugin in the interim: @argszero/cordis-plugin-goal-ask-guard reproduces the same semantics with the primitive above at the agent/created seam; v0.1.1 added per-agent turn/end routing, the extra clear sites, listener disposal, and the shared denial copy (13/13 tests passing):
sh
npm install @argszero/cordis-plugin-goal-ask-guard
yaml
- insert:
    - id: goal-ask-guard
      name: '@argszero/cordis-plugin-goal-ask-guard'
  1. Install plugins through the market: add and update guard plugins like this from DSH Plugin Hub under Settings, Plugin Market, which is safer than hand-writing mount lines into the profile because a failure rolls the manifest back. If you are also debugging other guard-type backstops (repeat calls, runaway arguments), see runaway tool-call argument troubleshooting.

DSH plugin troubleshooting notes

Do not blame the model alone — line up all three layers to see whether the answer ever reached the model's context; that is the key discriminator here. Six points to keep in mind when a DeepSeek Harness plugin hits an autonomous goal round that asks a question:

  1. Do not blame the model alone: inspect the inner receipt, the outer tool/result, and whether the program wrote the value back into context, as three separate layers.
  2. DELEGATED_CALLER cannot reach autonomous rounds: a goal round shares the user's root, so it is not restricted like a subagent.
  3. Do not swap the guard for restrict(): hiding the tool leaves the model unable to tell why, whereas a catchable denial lets it move to update_goal(blocked).
  4. Err on the side of more clear sites: besides turn/end there are session-start, competing input, superseded, and disposed.
  5. Mind scoping on multi-agent hosts: the window is per agent, so avoid a global turn/end listener.
  6. In Code Mode the answer must be written back explicitly: that is the other half of the root cause, independent of the guard, since tool return values are not re-fed automatically.
DSH Plugin Hub plugin market: install a goal round ask guard plugin, check versions and updates

Sources: Discussion #6074, cordis-plugin-goal-ask-guard, npm @argszero/cordis-plugin-goal-ask-guard.

FAQ

Why does the model in a DeepSeek Harness goal round behave as if it never received my answer to ask_user_question?

In a DeepSeek Harness goal round the model never receives your answer because two independent facts combine. (1) A goal round shares the same runtime root as the user, and dsh-user-questions only rejects live subagents (DELEGATED_CALLER), so nothing stops an autonomous round from asking interactively; (2) in Code Mode only the program's own logs and returns re-enter model context, so an answer nested inside run_code is silently discarded. In the recorded incident the outer tool/result returned only "asked" both times, after which the model ran 30+ goal rounds, each ending with "Awaiting your two decisions" (source: Discussion #6074).

In DeepSeek Harness, why does a pending ask_user_question never time out and instead stall the whole goal round?

In DeepSeek Harness a pending question declares no timeout budget, so the driver waits indefinitely for an answer that may never come. In the recorded incident the first answer took about 8.5 minutes to arrive, and the driver stalled for the whole interval. Nothing cancels it automatically, and nothing tells the model that no one is going to reply, so the autonomous round simply hangs (source: Discussion #6074).

For a DeepSeek Harness goal round, what is the correct fix and why not hide ask_user_question with tools.restrict()?

In a DeepSeek Harness goal round the right landing point is an agent-scoped ctx.tools.guard(): the guard returns a string to deny that execution, and a guard registered through agent.ctx applies only to that agent, which matches the "deny only during autonomous rounds" semantics exactly. It is preferable to restrict()-hiding because a guard denial is a **catchable ToolCallError**: the model can see that the tool is disabled for this turn and switch to recording the need through update_goal(blocked) or push on, instead of facing a tool that silently vanished (source: Discussion #6074).

Has DeepSeek Harness fixed this upstream, and what community plugin can I use in the meantime?

DeepSeek Harness has a fix ready as an in-tree branch (packages/goal/goal-round-driver/ gains DriverState.askGuardDispose, GOAL_ROUND_ASK_DENIAL, and installAskGuard / clearAskGuard; 88 insertions across 4 files; the package suite passes 57/57), but its author lacks pull-request permission on the repository, so a maintainer has to open it from the branch. Meanwhile the community plugin @argszero/cordis-plugin-goal-ask-guard (v0.1.1, 13/13 tests) reproduces the same semantics with the same existing primitive without touching driver source (source: Discussion #6074).

Related Terms

goal round
One round of execution driven by continuation under autonomous goal mode, identified by message source `source.kind === 'goal'` with `round > 0`. It shares the same runtime root as user interaction, so it is not restricted the way a subagent is.https://github.com/deepseek-ai/deepseek-harness/discussions/6074
ctx.tools.guard()
An existing core tool-guard primitive that registers a monotonic guard after the extensible tools/pre-execute waterfall; returning a string denies that execution. Registered through agent.ctx it applies only to that agent, and the denial is not hidden from the tool menu but surfaced as a catchable ToolCallError.https://github.com/deepseek-ai/deepseek-harness/discussions/6074
Code Mode return-value semantics
In Code Mode the model only sees the program's own logs and returns, so a tool's return value must be written back explicitly by the program; otherwise a successful execution never re-enters model context.https://github.com/deepseek-ai/deepseek-harness/discussions/6074

Sources