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.