DeepSeek Harness plan mode submits while subagents still run

TroubleshootingPublished 2026-10-20Author: DeepSeek Plugin Market
DeepSeek Harnessplan modesubagentexit_plan_modeplan review
DeepSeek Harness exit_plan_mode checks four preconditions and none involve subagents, so plans built on unfinished research reach review. Three gate options.

Nothing is racing in DeepSeek Harness — the check is simply absent. None of exit_plan_mode's four preconditions involves delegation, and the default policy text never mentions in-flight subagents. The model can only poll; when polling returns running and submission is still allowed, the plan goes through (source).

One real DeepSeek Harness plan mode trace: running at 138, submitted at 139

Bottom line: the interesting part is not disobedience — the model did the only thing the current tool surface allows: poll, see running, then follow the policy rule that the response must end with a single tool call.

  1. In plan mode, start two read-only research subagents from one assistant message; they run in the background by default. Expected: both return ids and the parent turn continues.
  2. Let the parent keep working and watch list_agents. Expected: repeated [running]; the trace in the report polled 8 times.
  3. Before either report lands, let the parent submit a plan. Expected: exit_plan_mode succeeds and enters review, with no "delegations still running" hint anywhere in the UI.
  4. Read the parent's reasoning. Expected: it says explicitly that those findings will be folded in during implementation — the model knows its inputs are incomplete.
  5. After the user dismisses, wait for the reports and the parent immediately submits v2, which is approved. Expected: the v1 review was pure noise for the user.
  6. Walk the session events and compare tool/call against plan/mode timestamps. Expected: the submission precedes the end of the subagent turn, not follows it.

Subagent lifecycle and diagnostics are a separate class of problem; see subagent diagnostics and lifecycle.

Why adding a prompt cannot stop it

Bottom line: prompts and execution are separate paths — policy text can say "wait first", but whether a tool call is accepted is decided in code, with no enforcement link between them.

  1. Read the default plan:policy text: it lists what a complete plan must cover (goals, change grouping, interfaces and data flow, boundaries, failure modes, tests, acceptance) and says nothing about delegation.
  2. Read the tool description: subagent says it returns an id immediately and notifies you when it finishes, so the model's only waiting primitive is "end the turn and wait for the notification".
  3. The same policy also requires that, when submitting, exit_plan_mode be the only and last tool call in that response — which makes "list once, then submit" look like a reasonable path.
  4. Conclusion: changing wording alone still allows submission after a [running] result; only a failing call keeps the plan out of review.
  5. Check what delegation guidance remains in the release: the discussion found only "keep doing useful work while they run", with no "end the turn when all that is left is waiting". Expected: you can find the same gap in your profile text.
  6. Run your own control: add "do not submit while delegations are in flight" to the policy and replay the trace. Expected: submission can still happen, which is the evidence that a hard gate is required.

Root cause: four checks in execute, and a projection that hides one-shot work

Bottom line: the checks are caller agent present, plan mode active for the session, plan starts with #, and userQuestions available; subagents, delegation directories and inboxes are never read.

  1. Compare against the source review in the discussion: execute runs those four checks in that order and rejects only when one fails. Expected: you find the same structure in your local version.
  2. Be precise about "running": it is the state of the subagent's own turn, not proof that its report has not been delivered. A guard that conflates them will misfire.
  3. Check the list_agents projection: subagents whose mode is not continuable return empty. Expected: one-shot delegations can neither be waited on nor listed.
  4. Look for a blocking wait: the tree does have job tools with wait, but jobs and delegations are separate surfaces, so it cannot join a subagent.
  5. Net result: no enforced check, no visible waiting primitive, no list covering every delegation. Three gaps that stack.

Fix 1: deny in tools/pre-execute with a DSH plugin

Bottom line: no core change and shippable today — before each call, reject when the caller is a given agent, the tool is exit_plan_mode, and that agent has a subagent turn running.

  1. Write a DSH plugin that registers a tools/pre-execute listener. Expected: every tool call passes through it.
  2. Check exec.name === 'exit_plan_mode' first and let everything else through. Expected: normal tool calls are unaffected.
  3. Use exec.agent to read its subagent list and filter entries whose turn state is running. Expected: you get ids and names.
  4. On a match, return deny with a reason such as "N delegations are still running; end this turn, wait for the settlement notification, then submit the complete plan". Expected: the model reads that at the moment of the call, not from memory of a prompt.
  5. Attach the release condition to subagent settlement events, never to a countdown. Expected: you do not trade premature submission for a permanent block.
  6. State the coverage in the reason: it applies only to continuable subagents that can be proven running, and one-shot delegations are outside the visible set. Expected: readers do not overestimate it.
  7. Replay the trace from the first section. Expected: the call at step 139 is rejected, the parent ends its turn, and submission happens after the reports land.
  8. Observe one false positive: a call made just as a subagent settles but before the state refreshes. Expected: at most one extra turn of waiting, not a permanent denial.

Fix 2: check inside the DeepSeek Harness execute path and reject

Bottom line: if you can change core, put the gate in the tool itself — read live descendants before entering review and fail outright when any are running, so the review UI never opens.

  1. After the existing four checks in execute, add a step that reads the current agent's subagent list. Expected: running delegations are visible.
  2. Fail whenever a running delegation exists, and list the ids and names in the error. Expected: both user and model can see who is being waited on.
  3. Make sure this happens before asking the user, not after review with a rollback. Expected: the plan never reaches the review screen.
  4. Keep the approved output shape unchanged and only add a failure path. Expected: callers need no adaptation.
  5. Update plan:policy at the same time to require that a plan cover all delegated research and wait for in-flight work. Expected: the model does not have to learn by trial and error.
  6. Add a regression case: start a background subagent and call exit_plan_mode immediately; assert failure with the subagent id in the message.
  7. Add a one-shot case too. Expected: it is rejected as well, because the core can read all descendants rather than a filtered projection.
  8. Verify no plan text was written to the review record after the rejection. Expected: the user never sees v1.

Fix 3: disable the submit button in the DeepSeek Harness UI

Bottom line: this is the weakest fallback — it reduces misclicks but does not stop the model — so use it as an extra layer, not as a replacement.

  1. Subscribe the plan review screen to the session's subagent state. Expected: the button state follows the running count.
  2. Grey out approve or submit while anything is running, and show the number of in-flight delegations next to it. Expected: the user understands why it is disabled.
  3. Provide a read-only "view anyway" entry that previews the plan without approval. Expected: review is not completely blocked.
  4. State the limitation plainly: the model's exit_plan_mode call ignores the button, and the plan may already exist. Expected: nobody treats this as a security boundary.
  5. Replay the same trace. Expected: the UI shows two in-flight delegations, v1 cannot be approved, and the button unlocks after the reports land.
  6. Reload and retry. Expected: state rebuilds from session events rather than component memory, and the button stays disabled while anything runs.

Where the DeepSeek Harness gate belongs

Bottom line: the three intercept points differ in whether they can stop the model and in what they cannot see. Pick path one for immediate effect, path two for the correct fix, and treat path three as an addition.

PathIntercept pointStops the modelMain limitation
pre-execute denyBefore the tool callYesCannot see one-shot work; can only reject, not wait
Check inside executeInside the toolYes, and most thoroughlyCore change, conflicts on upgrade
UI disableReview screenNoManual fallback only

What a badly written guard turns into

Bottom line: a gate that releases on a timer, or claims to see every delegation, converts a missed check into a different failure you will have to debug later.

  1. Do not rely on "list in-flight delegations in the review UI" alone; the trace already shows the model submitting anyway.
  2. The guard must release on a state change (subagent settlement), never on a timeout.
  3. "running" means the subagent turn is executing; it is not strictly the same as "the report has not arrived", so do not mix the two in user-facing text.
  4. One-shot delegations are a known blind spot; any guard claiming full coverage is inaccurate.
  5. The checks and code locations above come from community source review in the discussion; upstream has not fixed it, so treat this as temporary.

DeepSeek Harness FAQ

Bottom line: these four answers cover coverage, release conditions, and the difference between a hint and a gate.

Q: Does the guard block legitimate submissions forever if a subagent never finishes? A: No, but it will wait as long as the subagent does. That is why the release condition must be tied to settlement and why you need a way to cancel the subagent, not a timeout that silently lets the plan through.

Q: Can I combine the pre-execute gate with the UI disable? A: Yes, and it is sensible: the gate stops the model, the disabled button prevents a manual approval of a plan that should not be reviewed yet.

Q: Why not just increase the number of polls? A: Polling cannot create a precondition that does not exist. The model can poll indefinitely and still submit the moment the policy tells it to emit a tool call.

Q: Does this affect plan mode with no subagents at all? A: No. With no delegations, both the pre-execute gate and the execute check pass through, and the UI has nothing to disable.

FAQ

A background subagent is still running, so why does exit_plan_mode accept the plan?

Because the DeepSeek Harness exit_plan_mode execute path checks only four things: the caller exists, plan mode is logically active for that session, the plan starts with a # heading, and the userQuestions channel is available. It never reads subagent state, so an unsettled delegation goes straight to review.

Can a deny from tools/pre-execute block a premature submission?

Yes — the DeepSeek Harness pre-execute hook is a waterfall that runs before every tool call and receives the calling agent and tool name, so it can return deny with a reason. It can only reject, not make the model wait, so the release condition must be actual subagent settlement, never a timer.

Why does a gate built on list_agents cover only half of the delegations?

Because the DeepSeek Harness list_agents projection returns an empty result for subagents whose mode is not continuable. One-shot subagents are traversed but never listed, so the guard can only prove that the visible ones are running; the message must state that blind spot.

Is listing in-flight subagents in the review UI enough?

No — the DeepSeek Harness report's trace shows the model submitting after list_agents returned running, so a hint does not stop a stale plan from reaching review. Handing the decision to the user only downgrades a system problem to manual cleanup.

What does checking inside exit_plan_mode's execute buy over a plugin intercept?

The DeepSeek Harness execute path can read all descendants, including the one-shot delegations the list_agents projection cannot see, so coverage is better. The price is a core change that must be re-aligned on every upgrade, which suits teams that can maintain a branch.

Related Terms

exit_plan_mode
The tool that submits a plan for review while in plan mode. It takes a single plan argument and returns an approved boolean. The current implementation checks only the caller, the mode state, the heading format and the question channel.— https://github.com/deepseek-ai/deepseek-harness/discussions/9876
tools/pre-execute
A waterfall hook that runs before each tool call. It receives the tool name, the parsed arguments and the calling agent, and may return allow, deny, cancel or ask. It fits admission checks based on caller state.— https://github.com/deepseek-ai/deepseek-harness/discussions/9876
continuable subagent
A subagent mode that stays interactive and therefore appears in the list_agents projection. One-shot subagents are absent from that projection, so any guard built on list_agents cannot see them.— https://github.com/deepseek-ai/deepseek-harness/discussions/9876

Sources