From 07b3a553f780dd2edbcd2d154aea1551a37c3b5f Mon Sep 17 00:00:00 2001 From: Prateek Date: Fri, 13 Feb 2026 21:00:58 +0530 Subject: [PATCH] fix: allow notify reactions when auto is false, clean up reserved IDs on spawn failure - Reactions with action: "notify" bypass the auto: false gate so merge-ready notifications fire for approved-and-green config - All spawn failure paths now delete the reserved session metadata file Co-Authored-By: Claude Opus 4.6 --- packages/core/src/lifecycle-manager.ts | 13 +++++-- packages/core/src/session-manager.ts | 53 +++++++++++++++++--------- 2 files changed, 43 insertions(+), 23 deletions(-) diff --git a/packages/core/src/lifecycle-manager.ts b/packages/core/src/lifecycle-manager.ts index 2558c21cb..e4b597b63 100644 --- a/packages/core/src/lifecycle-manager.ts +++ b/packages/core/src/lifecycle-manager.ts @@ -418,8 +418,11 @@ export function createLifecycleManager(deps: LifecycleManagerDeps): LifecycleMan ? { ...globalReaction, ...projectReaction } : globalReaction; - if (reactionConfig && reactionConfig.auto !== false && reactionConfig.action) { - await executeReaction(event, reactionKey, reactionConfig as ReactionConfig); + if (reactionConfig && reactionConfig.action) { + // auto: false skips automated agent actions but still allows notifications + if (reactionConfig.auto !== false || reactionConfig.action === "notify") { + await executeReaction(event, reactionKey, reactionConfig as ReactionConfig); + } } } } @@ -467,8 +470,10 @@ export function createLifecycleManager(deps: LifecycleManagerDeps): LifecycleMan const reactionKey = eventToReactionKey("summary.all_complete"); if (reactionKey) { const reactionConfig = config.reactions[reactionKey]; - if (reactionConfig && reactionConfig.auto !== false && reactionConfig.action) { - await executeReaction(event, reactionKey, reactionConfig as ReactionConfig); + if (reactionConfig && reactionConfig.action) { + if (reactionConfig.auto !== false || reactionConfig.action === "notify") { + await executeReaction(event, reactionKey, reactionConfig as ReactionConfig); + } } } } diff --git a/packages/core/src/session-manager.ts b/packages/core/src/session-manager.ts index 2a4094197..c06aca308 100644 --- a/packages/core/src/session-manager.ts +++ b/packages/core/src/session-manager.ts @@ -192,28 +192,38 @@ export function createSessionManager(deps: SessionManagerDeps): SessionManager { // Create workspace (if workspace plugin is available) let workspacePath = project.path; if (plugins.workspace) { - const wsInfo = await plugins.workspace.create({ - projectId: spawnConfig.projectId, - project, - sessionId, - branch, - }); - workspacePath = wsInfo.path; + try { + const wsInfo = await plugins.workspace.create({ + projectId: spawnConfig.projectId, + project, + sessionId, + branch, + }); + workspacePath = wsInfo.path; - // Run post-create hooks — clean up workspace on failure (skip if project root) - if (plugins.workspace.postCreate) { - try { - await plugins.workspace.postCreate(wsInfo, project); - } catch (err) { - if (workspacePath !== project.path) { - try { - await plugins.workspace.destroy(workspacePath); - } catch { - /* best effort */ + // Run post-create hooks — clean up workspace on failure + if (plugins.workspace.postCreate) { + try { + await plugins.workspace.postCreate(wsInfo, project); + } catch (err) { + if (workspacePath !== project.path) { + try { + await plugins.workspace.destroy(workspacePath); + } catch { + /* best effort */ + } } + throw err; } - throw err; } + } catch (err) { + // Clean up reserved session ID on workspace failure + try { + deleteMetadata(config.dataDir, sessionId, false); + } catch { + /* best effort */ + } + throw err; } } @@ -242,7 +252,7 @@ export function createSessionManager(deps: SessionManagerDeps): SessionManager { }, }); } catch (err) { - // Clean up workspace if agent config or runtime creation failed + // Clean up workspace and reserved ID if agent config or runtime creation failed if (plugins.workspace && workspacePath !== project.path) { try { await plugins.workspace.destroy(workspacePath); @@ -250,6 +260,11 @@ export function createSessionManager(deps: SessionManagerDeps): SessionManager { /* best effort */ } } + try { + deleteMetadata(config.dataDir, sessionId, false); + } catch { + /* best effort */ + } throw err; }