Skip to content

Commit 043d640

Browse files
committed
fix: notify agent session on background exec completion by default
Previously, successful background exec processes with empty output would silently complete without notifying the parent agent session. This caused a major UX gap in multi-agent orchestration workflows where an orchestrator agent dispatches coding agents (Claude Code, Codex) in background — the orchestrator was never woken up on completion, leaving users waiting indefinitely. Changes: - Default notifyOnExitEmptySuccess to true (was false) Background processes that complete successfully now always enqueue a system event, even when stdout is empty. Agents should always know when their dispatched work finishes. - Align runExecProcess default with createExecTool (both use !== false) - Reduce exec exit wake coalesce delay to 50ms (was 250ms) Exec completion is time-sensitive for orchestration flows. - Update tests and config docs to reflect new defaults Users who want the old behavior can set: tools.exec.notifyOnExitEmptySuccess: false Fixes #18237
1 parent 36f0f21 commit 043d640

5 files changed

Lines changed: 14 additions & 8 deletions

File tree

src/agents/bash-tools.exec-runtime.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,10 @@ function maybeNotifyOnExit(session: ProcessSession, status: "completed" | "faile
222222
: `Exec ${status} (${session.id.slice(0, 8)}, ${exitLabel})`;
223223
enqueueSystemEvent(summary, { sessionKey });
224224
requestHeartbeatNow(
225-
scopedHeartbeatWakeOptions(sessionKey, { reason: `exec:${session.id}:exit` }),
225+
scopedHeartbeatWakeOptions(sessionKey, {
226+
reason: `exec:${session.id}:exit`,
227+
coalesceMs: 50,
228+
}),
226229
);
227230
}
228231

@@ -321,7 +324,7 @@ export async function runExecProcess(opts: {
321324
scopeKey: opts.scopeKey,
322325
sessionKey: opts.sessionKey,
323326
notifyOnExit: opts.notifyOnExit,
324-
notifyOnExitEmptySuccess: opts.notifyOnExitEmptySuccess === true,
327+
notifyOnExitEmptySuccess: opts.notifyOnExitEmptySuccess !== false,
325328
exitNotified: false,
326329
child: undefined,
327330
stdin: undefined,

src/agents/bash-tools.exec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ export function createExecTool(
191191
);
192192
}
193193
const notifyOnExit = defaults?.notifyOnExit !== false;
194-
const notifyOnExitEmptySuccess = defaults?.notifyOnExitEmptySuccess === true;
194+
const notifyOnExitEmptySuccess = defaults?.notifyOnExitEmptySuccess !== false;
195195
const notifySessionKey = defaults?.sessionKey?.trim() || undefined;
196196
const approvalRunningNoticeMs = resolveApprovalRunningNoticeMs(defaults?.approvalRunningNoticeMs);
197197
// Derive agentId only when sessionKey is an agent session key.

src/agents/bash-tools.test.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -268,10 +268,12 @@ type NotifyNoopCase = LabeledCase & {
268268
notifyOnExitEmptySuccess: boolean;
269269
};
270270
const NOOP_NOTIFY_CASES: NotifyNoopCase[] = [
271-
withLabel("default behavior skips no-op completion events", { notifyOnExitEmptySuccess: false }),
272-
withLabel("explicitly enabling no-op completion emits completion events", {
271+
withLabel("default behavior emits completion events for empty-output success", {
273272
notifyOnExitEmptySuccess: true,
274273
}),
274+
withLabel("explicitly disabling no-op completion skips events", {
275+
notifyOnExitEmptySuccess: false,
276+
}),
275277
];
276278
const DISALLOWED_ELEVATION_CASES: DisallowedElevationCase[] = [
277279
withLabel("rejects elevated requests when not allowed", {
@@ -398,7 +400,7 @@ const runLongLogExpectationCase = async ({
398400
};
399401
const runNotifyNoopCase = async ({ label, notifyOnExitEmptySuccess }: NotifyNoopCase) => {
400402
const tool = createNotifyOnExitExecTool(
401-
notifyOnExitEmptySuccess ? { notifyOnExitEmptySuccess: true } : {},
403+
notifyOnExitEmptySuccess ? {} : { notifyOnExitEmptySuccess: false },
402404
);
403405

404406
const { status } = await runBackgroundCommandToCompletion(tool, shortDelayCmd);

src/config/schema.help.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@ export const FIELD_HELP: Record<string, string> = {
551551
"tools.exec.notifyOnExit":
552552
"When true (default), backgrounded exec sessions on exit and node exec lifecycle events enqueue a system event and request a heartbeat.",
553553
"tools.exec.notifyOnExitEmptySuccess":
554-
"When true, successful backgrounded exec exits with empty output still enqueue a completion system event (default: false).",
554+
"When true (default), successful backgrounded exec exits with empty output still enqueue a completion system event. Set to false to suppress these notifications.",
555555
"tools.exec.pathPrepend": "Directories to prepend to PATH for exec runs (gateway/sandbox).",
556556
"tools.exec.safeBins":
557557
"Allow stdin-only safe binaries to run without explicit allowlist entries.",

src/config/types.tools.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,8 @@ export type ExecToolConfig = {
254254
notifyOnExit?: boolean;
255255
/**
256256
* Also emit success exit notifications when a backgrounded exec has no output.
257-
* Default false to reduce context noise.
257+
* Default true — agents should always be notified when background tasks complete.
258+
* Set to false to suppress empty-success notifications and reduce context noise.
258259
*/
259260
notifyOnExitEmptySuccess?: boolean;
260261
/** apply_patch subtool configuration (experimental). */

0 commit comments

Comments
 (0)