Merge pull request #3825 from tw-yshuang/fix/delegated-child-session-early-failure-fallback

fix(delegate-task): harden child-session first-prompt fallback recovery
This commit is contained in:
YeonGyu-Kim
2026-05-15 19:06:43 +09:00
committed by GitHub
10 changed files with 585 additions and 46 deletions
+28 -7
View File
@@ -95,6 +95,11 @@ import {
resolveSubagentSpawnContext,
type SubagentSpawnContext,
} from "./subagent-spawn-limits"
import {
clearDelegatedChildSessionBootstrap,
registerDelegatedChildSessionBootstrap,
} from "../../shared/delegated-child-session-bootstrap"
import { settleAfterSessionIdle } from "../../hooks/shared/session-idle-settle"
type OpencodeClient = PluginInput["client"]
type ParentWakePromptContext = {
@@ -318,6 +323,12 @@ export class BackgroundManager {
}
}
private cleanupDelegatedSessionContext(sessionID: string): void {
clearDelegatedChildSessionBootstrap(sessionID)
this.modelFallbackControllerAccessor?.clearSessionFallbackChain(sessionID)
SessionCategoryRegistry.remove(sessionID)
}
async assertCanSpawn(parentSessionID: string): Promise<SubagentSpawnContext> {
const spawnContext = await resolveSubagentSpawnContext(this.client, parentSessionID, this.directory)
const maxDepth = getMaxSubagentDepth(this.config)
@@ -796,6 +807,14 @@ export class BackgroundManager {
return
}
registerDelegatedChildSessionBootstrap({
sessionID,
promptText: input.prompt,
fallbackChain: input.fallbackChain,
category: input.category,
modelFallbackControllerAccessor: this.modelFallbackControllerAccessor,
})
task.progress = {
toolCalls: 0,
lastUpdate: new Date(),
@@ -963,6 +982,7 @@ The fallback retry session is now created and can be inspected directly.
// Abort the session to prevent infinite polling hang
// Awaited to prevent dangling promise during subagent teardown (Bun/WebKit SIGABRT)
await this.abortSessionWithLogging(sessionID, "launch error cleanup")
this.cleanupDelegatedSessionContext(sessionID)
this.markForNotification(existingTask)
this.enqueueNotificationForParent(existingTask.parentSessionId, () => this.notifyParentSession(existingTask)).catch(err => {
@@ -1863,7 +1883,7 @@ The fallback retry session is now created and can be inspected directly.
}
this.rootDescendantCounts.delete(sessionID)
SessionCategoryRegistry.remove(sessionID)
this.cleanupDelegatedSessionContext(sessionID)
}
if (event.type === "session.status") {
@@ -2051,7 +2071,7 @@ The fallback retry session is now created and can be inspected directly.
}
this.scheduleTaskRemoval(task.id)
if (task.sessionId) {
SessionCategoryRegistry.remove(task.sessionId)
this.cleanupDelegatedSessionContext(task.sessionId)
}
// Update continuation marker for CLI run mode
@@ -2109,6 +2129,7 @@ The task was re-queued on a fallback model after a retryable failure.
this.clearSessionOutputObserved(previousSessionID)
this.clearSessionTodoObservation(previousSessionID)
subagentSessions.delete(previousSessionID)
this.cleanupDelegatedSessionContext(previousSessionID)
}
return retried
}
@@ -2272,7 +2293,7 @@ The task was re-queued on a fallback model after a retryable failure.
this.clearTaskHistoryWhenParentTasksGone(task.parentSessionId)
if (task.sessionId) {
subagentSessions.delete(task.sessionId)
SessionCategoryRegistry.remove(task.sessionId)
this.cleanupDelegatedSessionContext(task.sessionId)
}
log("[background-agent] Removed completed task from memory:", taskId)
}, TASK_CLEANUP_DELAY_MS)
@@ -2347,7 +2368,7 @@ The task was re-queued on a fallback model after a retryable failure.
// Awaited to prevent dangling promise during subagent teardown (Bun/WebKit SIGABRT)
await this.abortSessionWithLogging(task.sessionId, `task cancellation (${source})`)
SessionCategoryRegistry.remove(task.sessionId)
this.cleanupDelegatedSessionContext(task.sessionId)
}
removeTaskToastTracking(task.id)
@@ -2472,7 +2493,7 @@ The task was re-queued on a fallback model after a retryable failure.
// Awaited to prevent dangling promise during subagent teardown (Bun/WebKit SIGABRT)
await this.abortSessionWithLogging(task.sessionId, `task completion (${source})`)
SessionCategoryRegistry.remove(task.sessionId)
this.cleanupDelegatedSessionContext(task.sessionId)
}
// Update continuation marker for CLI run mode
@@ -2921,7 +2942,7 @@ The task was re-queued on a fallback model after a retryable failure.
removeTaskToastTracking(task.id)
this.scheduleTaskRemoval(task.id)
if (task.sessionId) {
SessionCategoryRegistry.remove(task.sessionId)
this.cleanupDelegatedSessionContext(task.sessionId)
}
// Update continuation marker for CLI run mode
@@ -3136,7 +3157,7 @@ The task was re-queued on a fallback model after a retryable failure.
for (const sessionID of trackedSessionIDs) {
subagentSessions.delete(sessionID)
SessionCategoryRegistry.remove(sessionID)
this.cleanupDelegatedSessionContext(sessionID)
}
this.concurrencyManager.clear()