Bug Description
Native subagent runs (via sessions_spawn runtime=subagent) complete successfully, but the parent agent never receives the child's output. The delivery_status field in task_runs remains pending indefinitely, and agent.wait on the child session returns status=timeout with empty output.
Root Cause
Two separate failure points in the completion/capture/delivery pipeline:
Failure Point 1 — Result not captured before session cleanup (Hermes, fixed)
File: dist/subagent-registry-CdtzK4_R.js
When a subagent run completes, the wait.resultText (the assistant's final output text) exists only transiently in the child session state before that session store is cleaned up. The parent-side completion capture fired before the result was frozen, causing the delivery payload to be empty.
Fix: In freezeRunResultAtCompletion (called from the announce flow), capture wait.resultText durably before session cleanup. Added caching of assistant text by runId in server-methods-Dvr1K7zh.js and preservation of resultText/replyText in normalized wait results in run-wait-cmY6eTHK.js.
Failure Point 2 — Completed runs incorrectly identified as orphans (Operator, fixed)
File: dist/subagent-registry-CdtzK4_R.js
The function resolveSubagentRunOrphanReason returns "missing-session-entry" when the session store entry for a completed child run is missing. However, for completed runs (those with endedAt set), the session store is expected to be cleaned up after the run ends — this is normal cleanup, not an orphan condition. Only active/unended runs with missing session entries should be pruned.
Fix: In resolveSubagentRunOrphanReason, if sessionEntry is null AND entry.endedAt is set, return null (not an orphan) instead of "missing-session-entry":
// Before:
if (!sessionEntry) return "missing-session-entry";
// After:
if (!sessionEntry) {
// Session store gone after run ended — expected cleanup, not an orphan.
// prune only active/unended runs whose store is missing.
if (typeof params.entry.endedAt === "number") return null;
return "missing-session-entry";
}
Files Modified (dist patches — for reference only)
These are compiled/bundled dist files and not meant to be edited directly. The patches below are provided as reference implementations — they demonstrate the fix but should ideally be applied at source level and rebuilt.
| File |
Change |
Applied by |
subagent-registry-CdtzK4_R.js |
Freeze resultText + orphan check fix |
Hermes + Operator |
server-methods-Dvr1K7zh.js |
Cache assistant text by runId |
Hermes |
run-wait-cmY6eTHK.js |
Preserve resultText/replyText in normalized wait |
Hermes |
agent-runner.runtime-CjYlXxbm.js |
Emit CLI final text as resultText on lifecycle end |
Hermes |
Verification
Smoke test with marker SUBAGENT_E2E_FIXED_173504:
task_runs row: cc24b894-d70b-46aa-97ee-1a0f3b27417a
status: succeeded, delivery_status: delivered
agent.wait: status=ok (not timeout)
parent received marker as non-empty output ✓
DB Cleanup
5 stale rows with runtime=subagent, status=succeeded, delivery_status=pending (orphaned before fix) were updated to delivery_status=delivered.
Environment
- OpenClaw 2026.5.5 (macOS Darwin, node v22.22.1)
- Runtime: native subagent via
sessions_spawn runtime=subagent
- Task DB:
~/.openclaw/tasks/runs.sqlite
Expected Behavior (after fix)
- Child subagent run completes →
status=succeeded
delivery_status transitions to delivered (not stuck at pending)
- Parent agent's
agent.wait returns status=ok with the child's output text
- No orphan pruning of completed runs at restore time
Suggested Source-Level Fix
For the orphan-check issue specifically, the fix in resolveSubagentRunOrphanReason is a one-liner at the session-entry null check — add the endedAt guard to return null for completed runs. For the result capture issue, the proper fix requires ensuring wait.resultText is captured and frozen before session cleanup in the lifecycle flow.
Bug Description
Native subagent runs (via
sessions_spawn runtime=subagent) complete successfully, but the parent agent never receives the child's output. Thedelivery_statusfield intask_runsremainspendingindefinitely, andagent.waiton the child session returnsstatus=timeoutwith empty output.Root Cause
Two separate failure points in the completion/capture/delivery pipeline:
Failure Point 1 — Result not captured before session cleanup (Hermes, fixed)
File:
dist/subagent-registry-CdtzK4_R.jsWhen a subagent run completes, the
wait.resultText(the assistant's final output text) exists only transiently in the child session state before that session store is cleaned up. The parent-side completion capture fired before the result was frozen, causing the delivery payload to be empty.Fix: In
freezeRunResultAtCompletion(called from the announce flow), capturewait.resultTextdurably before session cleanup. Added caching of assistant text by runId inserver-methods-Dvr1K7zh.jsand preservation ofresultText/replyTextin normalized wait results inrun-wait-cmY6eTHK.js.Failure Point 2 — Completed runs incorrectly identified as orphans (Operator, fixed)
File:
dist/subagent-registry-CdtzK4_R.jsThe function
resolveSubagentRunOrphanReasonreturns"missing-session-entry"when the session store entry for a completed child run is missing. However, for completed runs (those withendedAtset), the session store is expected to be cleaned up after the run ends — this is normal cleanup, not an orphan condition. Only active/unended runs with missing session entries should be pruned.Fix: In
resolveSubagentRunOrphanReason, ifsessionEntryis null ANDentry.endedAtis set, returnnull(not an orphan) instead of"missing-session-entry":Files Modified (dist patches — for reference only)
These are compiled/bundled dist files and not meant to be edited directly. The patches below are provided as reference implementations — they demonstrate the fix but should ideally be applied at source level and rebuilt.
subagent-registry-CdtzK4_R.jsserver-methods-Dvr1K7zh.jsrun-wait-cmY6eTHK.jsagent-runner.runtime-CjYlXxbm.jsVerification
Smoke test with marker
SUBAGENT_E2E_FIXED_173504:DB Cleanup
5 stale rows with
runtime=subagent, status=succeeded, delivery_status=pending(orphaned before fix) were updated todelivery_status=delivered.Environment
sessions_spawn runtime=subagent~/.openclaw/tasks/runs.sqliteExpected Behavior (after fix)
status=succeededdelivery_statustransitions todelivered(not stuck atpending)agent.waitreturnsstatus=okwith the child's output textSuggested Source-Level Fix
For the orphan-check issue specifically, the fix in
resolveSubagentRunOrphanReasonis a one-liner at the session-entry null check — add theendedAtguard to returnnullfor completed runs. For the result capture issue, the proper fix requires ensuringwait.resultTextis captured and frozen before session cleanup in the lifecycle flow.