fix(background-agent): treat non-active session statuses as terminal to prevent parent session hang
Previously, pollRunningTasks() and checkAndInterruptStaleTasks() treated
any non-"idle" session status as "still running", which caused tasks with
terminal statuses like "interrupted" to be skipped indefinitely — both
for completion detection AND stale timeout. This made the parent session
hang forever waiting for an ALL COMPLETE notification that never came.
Extract isActiveSessionStatus() and isTerminalSessionStatus() that
classify session statuses explicitly. Only known active statuses
("busy", "retry", "running") protect tasks from completion/stale checks.
Known terminal statuses ("interrupted") trigger immediate completion.
Unknown statuses fall through to the standard idle/gone path with output
validation as a conservative default.
Introduced by: a0c93816 (2026-02-14), dc370f7f (2026-03-08)
This commit is contained in:
@@ -52,6 +52,7 @@ import { join } from "node:path"
|
||||
import { pruneStaleTasksAndNotifications } from "./task-poller"
|
||||
import { checkAndInterruptStaleTasks } from "./task-poller"
|
||||
import { removeTaskToastTracking } from "./remove-task-toast-tracking"
|
||||
import { isActiveSessionStatus, isTerminalSessionStatus } from "./session-status-classifier"
|
||||
import {
|
||||
detectRepetitiveToolUse,
|
||||
recordToolCall,
|
||||
@@ -900,18 +901,19 @@ export class BackgroundManager {
|
||||
task.progress.lastUpdate = new Date()
|
||||
|
||||
if (partInfo?.type === "tool" || partInfo?.tool) {
|
||||
const countedToolPartIDs = task.progress.countedToolPartIDs ?? []
|
||||
const countedToolPartIDs = task.progress.countedToolPartIDs ?? new Set<string>()
|
||||
const shouldCountToolCall =
|
||||
!partInfo.id ||
|
||||
partInfo.state?.status !== "running" ||
|
||||
!countedToolPartIDs.includes(partInfo.id)
|
||||
!countedToolPartIDs.has(partInfo.id)
|
||||
|
||||
if (!shouldCountToolCall) {
|
||||
return
|
||||
}
|
||||
|
||||
if (partInfo.id && partInfo.state?.status === "running") {
|
||||
task.progress.countedToolPartIDs = [...countedToolPartIDs, partInfo.id]
|
||||
countedToolPartIDs.add(partInfo.id)
|
||||
task.progress.countedToolPartIDs = countedToolPartIDs
|
||||
}
|
||||
|
||||
task.progress.toolCalls += 1
|
||||
@@ -1782,11 +1784,9 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
|
||||
}
|
||||
}
|
||||
|
||||
// Match sync-session-poller pattern: only skip completion check when
|
||||
// status EXISTS and is not idle (i.e., session is actively running).
|
||||
// When sessionStatus is undefined, the session has completed and dropped
|
||||
// from the status response — fall through to completion detection.
|
||||
if (sessionStatus && sessionStatus.type !== "idle") {
|
||||
// Only skip completion when session status is actively running.
|
||||
// Unknown or terminal statuses (like "interrupted") fall through to completion.
|
||||
if (sessionStatus && isActiveSessionStatus(sessionStatus.type)) {
|
||||
log("[background-agent] Session still running, relying on event-based progress:", {
|
||||
taskId: task.id,
|
||||
sessionID,
|
||||
@@ -1796,6 +1796,24 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
|
||||
continue
|
||||
}
|
||||
|
||||
// Explicit terminal non-idle status (e.g., "interrupted") — complete immediately,
|
||||
// skipping output validation (session will never produce more output).
|
||||
// Unknown statuses fall through to the idle/gone path with output validation.
|
||||
if (sessionStatus && isTerminalSessionStatus(sessionStatus.type)) {
|
||||
await this.tryCompleteTask(task, `polling (terminal session status: ${sessionStatus.type})`)
|
||||
continue
|
||||
}
|
||||
|
||||
// Unknown non-idle status — not active, not terminal, not idle.
|
||||
// Fall through to idle/gone completion path with output validation.
|
||||
if (sessionStatus && sessionStatus.type !== "idle") {
|
||||
log("[background-agent] Unknown session status, treating as potentially idle:", {
|
||||
taskId: task.id,
|
||||
sessionID,
|
||||
sessionStatus: sessionStatus.type,
|
||||
})
|
||||
}
|
||||
|
||||
// Session is idle or no longer in status response (completed/disappeared)
|
||||
const completionSource = sessionStatus?.type === "idle"
|
||||
? "polling (idle status)"
|
||||
|
||||
Reference in New Issue
Block a user