From 25dcd2a3f2506239ba0ea0c1d6def20e6811a859 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sun, 1 Feb 2026 21:24:52 +0900 Subject: [PATCH] fix(background-agent): prevent concurrency slot leaks on task startup failures Unify slot ownership: processKey() owns the slot until task.concurrencyKey is set. Previously, startTask() released the slot on errors (session.create catch, createResult.error), but processKey() catch block didn't release, causing slot leaks when errors occurred between acquire() and task.concurrencyKey assignment. Changes: - Remove all pre-transfer release() calls in startTask() - Add conditional release in processKey() catch: only if task.concurrencyKey not set - Add validation for createResult.data?.id to catch malformed API responses This fixes 'Task failed to start within timeout' errors caused by exhausted concurrency slots that were never released. --- src/features/background-agent/manager.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/features/background-agent/manager.ts b/src/features/background-agent/manager.ts index aa4922783..28b5b6e43 100644 --- a/src/features/background-agent/manager.ts +++ b/src/features/background-agent/manager.ts @@ -200,6 +200,11 @@ export class BackgroundManager { await this.startTask(item) } catch (error) { log("[background-agent] Error starting task:", error) + // Release concurrency slot if startTask failed and didn't release it itself + // This prevents slot leaks when errors occur after acquire but before task.concurrencyKey is set + if (!item.task.concurrencyKey) { + this.concurrencyManager.release(key) + } } queue.shift() @@ -240,16 +245,16 @@ export class BackgroundManager { query: { directory: parentDirectory, }, - }).catch((error) => { - this.concurrencyManager.release(concurrencyKey) - throw error }) if (createResult.error) { - this.concurrencyManager.release(concurrencyKey) throw new Error(`Failed to create background session: ${createResult.error}`) } + if (!createResult.data?.id) { + throw new Error("Failed to create background session: API returned no session ID") + } + const sessionID = createResult.data.id subagentSessions.add(sessionID)