Merge pull request #3455 from CHLK/fix/cli-run-premature-exit-with-background-tasks
fix(cli-run): prevent premature exit when background tasks are active
This commit is contained in:
@@ -20,6 +20,11 @@ export async function checkCompletionConditions(ctx: RunContext): Promise<boolea
|
||||
return false
|
||||
}
|
||||
|
||||
if (continuationState.hasActiveBackgroundTaskMarker) {
|
||||
logWaiting(ctx, continuationState.activeHookMarkerReason ?? "background tasks are active")
|
||||
return false
|
||||
}
|
||||
|
||||
if (!await areAllChildrenIdle(ctx)) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface ContinuationState {
|
||||
hasActiveRalphLoop: boolean
|
||||
hasHookMarker: boolean
|
||||
hasTodoHookMarker: boolean
|
||||
hasActiveBackgroundTaskMarker: boolean
|
||||
hasActiveHookMarker: boolean
|
||||
activeHookMarkerReason: string | null
|
||||
}
|
||||
@@ -32,6 +33,7 @@ export async function getContinuationState(
|
||||
hasActiveRalphLoop: hasActiveRalphLoopContinuation(directory, sessionID),
|
||||
hasHookMarker: marker !== null,
|
||||
hasTodoHookMarker: marker?.sources.todo !== undefined,
|
||||
hasActiveBackgroundTaskMarker: marker?.sources["background-task"]?.state === "active",
|
||||
hasActiveHookMarker: isContinuationMarkerActive(marker),
|
||||
activeHookMarkerReason: getActiveContinuationMarkerReason(marker),
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ import {
|
||||
startAttempt,
|
||||
} from "./attempt-lifecycle"
|
||||
import { registerManagerForCleanup, unregisterManagerForCleanup } from "./process-cleanup"
|
||||
import { setContinuationMarkerSource } from "../../features/run-continuation-state"
|
||||
import {
|
||||
findNearestMessageExcludingCompaction,
|
||||
resolvePromptContextFromSessionMessages,
|
||||
@@ -449,6 +450,9 @@ export class BackgroundManager {
|
||||
spawnReservation.commit()
|
||||
this.markPreStartDescendantReservation(task)
|
||||
|
||||
// Signal CLI run mode that background tasks are active
|
||||
this.updateBackgroundTaskMarker(input.parentSessionID)
|
||||
|
||||
// Trigger processing (fire-and-forget)
|
||||
void this.processKey(key)
|
||||
|
||||
@@ -512,6 +516,9 @@ export class BackgroundManager {
|
||||
await this.abortSessionWithLogging(item.task.sessionId, "startTask error cleanup")
|
||||
}
|
||||
|
||||
// Update continuation marker for CLI run mode
|
||||
this.updateBackgroundTaskMarker(item.task.parentSessionID)
|
||||
|
||||
this.markForNotification(item.task)
|
||||
this.enqueueNotificationForParent(item.task.parentSessionId, () => this.notifyParentSession(item.task)).catch(err => {
|
||||
log("[background-agent] Failed to notify on startTask error:", err)
|
||||
@@ -814,6 +821,21 @@ The fallback retry session is now created and can be inspected directly.
|
||||
return tasks
|
||||
}
|
||||
|
||||
private updateBackgroundTaskMarker(parentSessionID: string): void {
|
||||
const tasks = this.getTasksByParentSession(parentSessionID)
|
||||
const activeTasks = tasks.filter(t => t.status === "running" || t.status === "pending")
|
||||
if (activeTasks.length > 0) {
|
||||
setContinuationMarkerSource(
|
||||
this.directory, parentSessionID, "background-task", "active",
|
||||
`${activeTasks.length} background task(s) active`,
|
||||
)
|
||||
} else {
|
||||
setContinuationMarkerSource(
|
||||
this.directory, parentSessionID, "background-task", "idle",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
getAllDescendantTasks(sessionID: string): BackgroundTask[] {
|
||||
const result: BackgroundTask[] = []
|
||||
const directChildren = this.getTasksByParentSession(sessionID)
|
||||
@@ -1525,6 +1547,11 @@ The fallback retry session is now created and can be inspected directly.
|
||||
SessionCategoryRegistry.remove(task.sessionId)
|
||||
}
|
||||
|
||||
// Update continuation marker for CLI run mode
|
||||
if (task.parentSessionID) {
|
||||
this.updateBackgroundTaskMarker(task.parentSessionID)
|
||||
}
|
||||
|
||||
this.markForNotification(task)
|
||||
this.enqueueNotificationForParent(task.parentSessionId, () => this.notifyParentSession(task)).catch(err => {
|
||||
log("[background-agent] Error in notifyParentSession for errored task:", { taskId: task.id, error: err })
|
||||
@@ -1824,6 +1851,11 @@ The task was re-queued on a fallback model after a retryable failure.
|
||||
|
||||
removeTaskToastTracking(task.id)
|
||||
|
||||
// Update continuation marker for CLI run mode
|
||||
if (task.parentSessionID) {
|
||||
this.updateBackgroundTaskMarker(task.parentSessionID)
|
||||
}
|
||||
|
||||
if (options?.skipNotification) {
|
||||
this.cleanupPendingByParent(task)
|
||||
this.scheduleTaskRemoval(task.id)
|
||||
@@ -1942,6 +1974,11 @@ The task was re-queued on a fallback model after a retryable failure.
|
||||
SessionCategoryRegistry.remove(task.sessionId)
|
||||
}
|
||||
|
||||
// Update continuation marker for CLI run mode
|
||||
if (task.parentSessionID) {
|
||||
this.updateBackgroundTaskMarker(task.parentSessionID)
|
||||
}
|
||||
|
||||
try {
|
||||
await this.enqueueNotificationForParent(task.parentSessionId, () => this.notifyParentSession(task))
|
||||
log(`[background-agent] Task completed via ${source}:`, task.id)
|
||||
@@ -2178,6 +2215,10 @@ The task was re-queued on a fallback model after a retryable failure.
|
||||
}
|
||||
}
|
||||
this.cleanupPendingByParent(task)
|
||||
// Update continuation marker for CLI run mode
|
||||
if (task.parentSessionID) {
|
||||
this.updateBackgroundTaskMarker(task.parentSessionID)
|
||||
}
|
||||
this.markForNotification(task)
|
||||
this.enqueueNotificationForParent(task.parentSessionId, () => this.notifyParentSession(task)).catch(err => {
|
||||
log("[background-agent] Error in notifyParentSession for stale-pruned task:", { taskId: task.id, error: err })
|
||||
@@ -2240,6 +2281,11 @@ The task was re-queued on a fallback model after a retryable failure.
|
||||
SessionCategoryRegistry.remove(task.sessionId)
|
||||
}
|
||||
|
||||
// Update continuation marker for CLI run mode
|
||||
if (task.parentSessionID) {
|
||||
this.updateBackgroundTaskMarker(task.parentSessionID)
|
||||
}
|
||||
|
||||
this.markForNotification(task)
|
||||
this.enqueueNotificationForParent(task.parentSessionId, () => this.notifyParentSession(task)).catch(err => {
|
||||
log("[background-agent] Error in notifyParentSession for crashed task:", { taskId: task.id, error: err })
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export type ContinuationMarkerSource = "todo" | "stop"
|
||||
export type ContinuationMarkerSource = "todo" | "stop" | "background-task"
|
||||
|
||||
export type ContinuationMarkerState = "idle" | "active" | "stopped"
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ export async function injectContinuation(args: {
|
||||
}
|
||||
|
||||
const hasRunningBgTasks = backgroundManager
|
||||
? backgroundManager.getTasksByParentSession(sessionID).some((task: { status: string }) => task.status === "running")
|
||||
? backgroundManager.getTasksByParentSession(sessionID).some((task: { status: string }) => task.status === "running" || task.status === "pending")
|
||||
: false
|
||||
|
||||
if (hasRunningBgTasks) {
|
||||
|
||||
@@ -71,7 +71,7 @@ export async function handleSessionIdle(args: {
|
||||
}
|
||||
|
||||
const hasRunningBgTasks = backgroundManager
|
||||
? backgroundManager.getTasksByParentSession(sessionID).some((task: { status: string }) => task.status === "running")
|
||||
? backgroundManager.getTasksByParentSession(sessionID).some((task: { status: string }) => task.status === "running" || task.status === "pending")
|
||||
: false
|
||||
|
||||
if (hasRunningBgTasks) {
|
||||
|
||||
Reference in New Issue
Block a user