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 <noreply@anthropic.com>
This commit is contained in:
Prateek 2026-02-13 21:00:58 +05:30
parent 65787f08e3
commit 07b3a553f7
2 changed files with 43 additions and 23 deletions

View File

@ -418,8 +418,11 @@ export function createLifecycleManager(deps: LifecycleManagerDeps): LifecycleMan
? { ...globalReaction, ...projectReaction } ? { ...globalReaction, ...projectReaction }
: globalReaction; : globalReaction;
if (reactionConfig && reactionConfig.auto !== false && reactionConfig.action) { if (reactionConfig && reactionConfig.action) {
await executeReaction(event, reactionKey, reactionConfig as ReactionConfig); // 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"); const reactionKey = eventToReactionKey("summary.all_complete");
if (reactionKey) { if (reactionKey) {
const reactionConfig = config.reactions[reactionKey]; const reactionConfig = config.reactions[reactionKey];
if (reactionConfig && reactionConfig.auto !== false && reactionConfig.action) { if (reactionConfig && reactionConfig.action) {
await executeReaction(event, reactionKey, reactionConfig as ReactionConfig); if (reactionConfig.auto !== false || reactionConfig.action === "notify") {
await executeReaction(event, reactionKey, reactionConfig as ReactionConfig);
}
} }
} }
} }

View File

@ -192,28 +192,38 @@ export function createSessionManager(deps: SessionManagerDeps): SessionManager {
// Create workspace (if workspace plugin is available) // Create workspace (if workspace plugin is available)
let workspacePath = project.path; let workspacePath = project.path;
if (plugins.workspace) { if (plugins.workspace) {
const wsInfo = await plugins.workspace.create({ try {
projectId: spawnConfig.projectId, const wsInfo = await plugins.workspace.create({
project, projectId: spawnConfig.projectId,
sessionId, project,
branch, sessionId,
}); branch,
workspacePath = wsInfo.path; });
workspacePath = wsInfo.path;
// Run post-create hooks — clean up workspace on failure (skip if project root) // Run post-create hooks — clean up workspace on failure
if (plugins.workspace.postCreate) { if (plugins.workspace.postCreate) {
try { try {
await plugins.workspace.postCreate(wsInfo, project); await plugins.workspace.postCreate(wsInfo, project);
} catch (err) { } catch (err) {
if (workspacePath !== project.path) { if (workspacePath !== project.path) {
try { try {
await plugins.workspace.destroy(workspacePath); await plugins.workspace.destroy(workspacePath);
} catch { } catch {
/* best effort */ /* 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) { } 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) { if (plugins.workspace && workspacePath !== project.path) {
try { try {
await plugins.workspace.destroy(workspacePath); await plugins.workspace.destroy(workspacePath);
@ -250,6 +260,11 @@ export function createSessionManager(deps: SessionManagerDeps): SessionManager {
/* best effort */ /* best effort */
} }
} }
try {
deleteMetadata(config.dataDir, sessionId, false);
} catch {
/* best effort */
}
throw err; throw err;
} }