fix(historian): defer child session cleanup - #337
Conversation
Keep successful historian children until the age-gated orphan sweep so OpenCode's detached writers cannot race session deletion. Extend the default prompt budget to ten minutes and surface empty Task completions instead of silently returning nothing.
There was a problem hiding this comment.
1 issue found across 15 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/plugin/src/hooks/magic-context/hook-handlers.ts">
<violation number="1" location="packages/plugin/src/hooks/magic-context/hook-handlers.ts:537">
P2: `annotateEmptyTaskOutput` appends the "completed without a final text response" message to any `task` output whose string is empty or whitespace, even when it is not a completed task wrapper. The empty-string branch (`trim().length === 0`) falls through past the `EMPTY_COMPLETED_TASK_RESULT` guard, so a task that failed, aborted, or never produced a `<task state="completed">` wrapper is still labeled as a completed-but-empty subagent. That contradicts the stated scope (surface completed `task_result` that is empty) and feeds the model a misleading annotation. Make the guard consistent with the completed-task regex, or handle the bare-empty case separately.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| // boundary. The queue helper re-checks the read-only mid-turn signal, | ||
| // so this is a no-op until the assistant is actually idle. | ||
| await flushIgnoredMessages(typedInput.sessionID); | ||
| annotateEmptyTaskOutput(typedInput.tool, output); |
There was a problem hiding this comment.
P2: annotateEmptyTaskOutput appends the "completed without a final text response" message to any task output whose string is empty or whitespace, even when it is not a completed task wrapper. The empty-string branch (trim().length === 0) falls through past the EMPTY_COMPLETED_TASK_RESULT guard, so a task that failed, aborted, or never produced a <task state="completed"> wrapper is still labeled as a completed-but-empty subagent. That contradicts the stated scope (surface completed task_result that is empty) and feeds the model a misleading annotation. Make the guard consistent with the completed-task regex, or handle the bare-empty case separately.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/plugin/src/hooks/magic-context/hook-handlers.ts, line 537:
<comment>`annotateEmptyTaskOutput` appends the "completed without a final text response" message to any `task` output whose string is empty or whitespace, even when it is not a completed task wrapper. The empty-string branch (`trim().length === 0`) falls through past the `EMPTY_COMPLETED_TASK_RESULT` guard, so a task that failed, aborted, or never produced a `<task state="completed">` wrapper is still labeled as a completed-but-empty subagent. That contradicts the stated scope (surface completed `task_result` that is empty) and feeds the model a misleading annotation. Make the guard consistent with the completed-task regex, or handle the bare-empty case separately.</comment>
<file context>
@@ -533,6 +534,7 @@ export function createToolExecuteAfterHook(args: {
// boundary. The queue helper re-checks the read-only mid-turn signal,
// so this is a no-op until the assistant is actually idle.
await flushIgnoredMessages(typedInput.sessionID);
+ annotateEmptyTaskOutput(typedInput.tool, output);
if (typedInput.tool === "ctx_reduce") {
</file context>
|
Thank you, Cole — you diagnosed the important part of #336 early: prompt completion is not a barrier for OpenCode's detached child writes, so deleting the historian child inline can race the FK-backed part insert. You filed this before we landed our independent work, and the overlap is real. In parallel we merged a fix for #336 (edd6e06): it keeps the child and retires it through the age-gated orphan sweep, adds the late-part FK regression repro, composes the stale threshold from the full retry envelope plus a detached-writer grace, covers the dreamer-disabled sweep path, and honors keep_subagents. Two pieces of your PR stand on their own and we're extracting them into focused follow-ups with credit: the empty-task-result visibility hook (makes a completed native Task with an empty result visible to the caller — useful for #270-class diagnostics), and the fallback-chain-aware timer registration (narrows an over-retention window). Closing this PR as superseded on the main arm — the two extractions will land crediting you as co-author. |
…gnostics) Extracted from PR #337 (Cole Leavitt). A completed native task tool can return an empty <task_result> when its provider emitted reasoning only or context-fill truncation dropped the final output. Annotate such outputs with a diagnostic sentinel from the tool-execute-after hook so the caller can distinguish an empty result from a genuinely-empty tool. Co-Authored-By: Cole Leavitt <cole@unwrap.rs> Co-Authored-By: Alfonso <alfonso@cortexkit.io>
Extracted from PR #337 (Cole Leavitt). The historian child-sweep retention budget previously counted raw fallback_models entries, which over-counted blank, duplicate, or malformed entries and could retain a child longer than necessary. Derive the count from resolveFallbackChain(...).length so the budget reflects the actual normalized fallback chain. Co-Authored-By: Cole Leavitt <cole@unwrap.rs> Co-Authored-By: Alfonso <alfonso@cortexkit.io>
…th Cole Leavitt (empty-task-output visibility, fallback-chain timer retention, 30s test-timeout lock observability, opentui TDZ warmup) Co-Authored-By: Alfonso <alfonso@cortexkit.io>
Summary
keep_subagentsis enabled<task_result>is emptyCloses #336.
Defensively addresses the silent empty-result symptom in #270.
Root cause
OpenCode prompt completion and
session.idleare not global session-writer barriers. Detached step/summary writes can persist after either event. Immediatesession.deletetherefore races foreign-key-backedpartinserts.Verification
Notes
The sweep retains a minimum safety factor of 3 after accounting for all configured prompt candidates and retry layers. Recent/live children are not eligible.
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.Summary by cubic
Defers historian child-session cleanup to an age-gated orphan sweep to avoid racing OpenCode’s detached writes and FK insert failures. Separates historian vs. privacy sweeps so privacy windows stay short while historian cleanup uses its own retry/fallback budget. Also raises the historian timeout and surfaces empty native Task completions for debugging.
keep_subagentsis true.historian_timeout_ms × MAX_HISTORIAN_PROMPT_ATTEMPTS × (primary+suggested) × fallback_count.resolveFallbackChain) and derives privacy timeouts before scheduling.historian_timeout_msis600000; exportsMAX_HISTORIAN_PROMPT_ATTEMPTS = 3.<task_result>using a sentinel via the tool-execute-after hook.historian_timeout_msto300000. Expect historian child sessions to persist until their sweep; privacy-only children retain their shorter sweep window.Closes #336. Defensively addresses the empty-result symptom in #270.
Written for commit 61ec04c. Summary will update on new commits.
Greptile Summary
This PR defers historian child-session deletion to an age-gated maintenance sweep, preventing deletion from racing detached OpenCode writes.
keep_subagents.<task_result>is empty.Confidence Score: 5/5
The PR appears safe to merge.
No blocking failure remains.
Important Files Changed
Sequence Diagram
sequenceDiagram participant H as Historian runner participant O as OpenCode session participant W as Detached writers participant T as Maintenance timer participant D as OpenCode DB H->>O: Create and prompt child session O-->>H: Prompt completes H-->>O: Preserve child session W->>D: Persist remaining step/summary parts T->>D: Find historian children older than stale threshold T->>O: Delete stale child unless keep_subagentsReviews (3): Last reviewed commit: "fix(historian): preserve child privacy w..." | Re-trigger Greptile
Context used: