solvedfeed

Fix subagent ending its turn without reporting back (orchestration hang)

The problem

My orchestrator dispatched a subagent with Task, and the result came back as one line — "Let me check the queue layer next" — with no findings, no files, no status. The parent then sat waiting on a report that was never coming, because the subagent had already ended its turn and nothing wakes a settled subagent.

What didn't work

  • Re-prompting "please continue" — a fresh subagent starts with no context and re-reads everything, doubling the cost for the same missing report.
  • Increasing max_turns / timeouts — the subagent didn't run out of turns, it declared itself done.
  • Treating silence as failure and re-dispatching in a loop — you burn a worker per iteration and still never see the intermediate findings.

The fix

Make the report durable state on disk, and treat the final message as a bonus, not the deliverable:

// orchestrate.ts
import { readFile } from 'node:fs/promises';

function brief(runId: string, goal: string, owns: string) {
  return `
Goal: ${goal}
You own ${owns}. Do not touch anything else — other workers own the rest.

HARD RULE: before your turn ends, ALWAYS write your findings to .runs/${runId}.md
using the Write tool, in exactly this shape:

STATUS: done | blocked | partial
FILES: <paths changed or read that matter>
FINDINGS: <3-8 bullets, concrete, with file:line refs>
NEXT: <what you could not finish and why>

Your final message should ALSO contain that report. But the file is the source of truth.
If you are about to end your turn without the file, do not end — write the file.
`;
}

async function collect(runId: string, finalMessage: string) {
  const msgHasReport = finalMessage.includes('STATUS:');
  if (msgHasReport) return finalMessage;
  // Subagent settled without a report — read what it wrote to disk instead.
  const report = await readFile(`.runs/${runId}.md`, 'utf8').catch(() => null);
  return report ?? `RE-DISPATCH ${runId}: subagent ended with: "${finalMessage}"`;
}

The two changes that matter: the brief demands a file (Write survives even if the final message is truncated), and the orchestrator's check is on data, not on prose tone.

Why it works

It converts the one-shot turn boundary into durable state — the subagent's last actions are recorded on disk even when its final message is thin or it dies mid-task, and "is the work done?" is answered by reading a file rather than guessing from a one-line reply.

Continue reading — $1/month

The full fix, verbatim, plus every other solution in the feed.

raw markdown twin · ← back to feed