Skip to content

Commit 381e2ee

Browse files
GavinZaltaywtf
authored andcommitted
fix(exec): skip subagent exec heartbeat wake
1 parent af5f6a3 commit 381e2ee

2 files changed

Lines changed: 50 additions & 26 deletions

File tree

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,21 @@ describe("emitExecSystemEvent", () => {
565565
expect(enqueueSystemEventMock).not.toHaveBeenCalled();
566566
expect(requestHeartbeatMock).not.toHaveBeenCalled();
567567
});
568+
569+
it("skips heartbeat wake for subagent session keys", () => {
570+
emitExecSystemEvent("Exec finished", {
571+
sessionKey: "agent:main:subagent:abc-123",
572+
contextKey: "exec:run-sub",
573+
});
574+
575+
expect(enqueueSystemEventMock).toHaveBeenCalledWith("Exec finished", {
576+
sessionKey: "agent:main:subagent:abc-123",
577+
contextKey: "exec:run-sub",
578+
deliveryContext: undefined,
579+
trusted: false,
580+
});
581+
expect(requestHeartbeatMock).not.toHaveBeenCalled();
582+
});
568583
});
569584

570585
describe("formatExecFailureReason", () => {

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

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { isDangerousHostInheritedEnvVarName } from "../infra/host-env-security.j
1313
import { findPathKey, mergePathPrepend } from "../infra/path-prepend.js";
1414
import { enqueueSystemEvent } from "../infra/system-events.js";
1515
import { resolveEventSessionKey, scopedHeartbeatWakeOptions } from "../routing/session-key.js";
16+
import { isSubagentSessionKey } from "../sessions/session-key-utils.js";
1617
import type { ProcessSession } from "./bash-process-registry.js";
1718
import type { ExecToolDetails } from "./bash-tools.exec-types.js";
1819
import type { BashSandboxConfig } from "./bash-tools.shared.js";
@@ -344,19 +345,23 @@ function maybeNotifyOnExit(session: ProcessSession, status: "completed" | "faile
344345
deliveryContext: session.notifyDeliveryContext,
345346
trusted: false,
346347
});
347-
requestHeartbeat(
348-
scopedHeartbeatWakeOptions(
349-
sessionKey,
350-
{
351-
source: "exec-event",
352-
intent: "event",
353-
reason: "exec-event",
354-
coalesceMs: 0,
355-
},
356-
session.mainKey,
357-
session.sessionScope,
358-
),
359-
);
348+
// Subagent sessions receive exec results via process poll and announce flow;
349+
// the heartbeat would fall back to the main session and cause spurious wakes.
350+
if (!isSubagentSessionKey(sessionKey)) {
351+
requestHeartbeat(
352+
scopedHeartbeatWakeOptions(
353+
sessionKey,
354+
{
355+
source: "exec-event",
356+
intent: "event",
357+
reason: "exec-event",
358+
coalesceMs: 0,
359+
},
360+
session.mainKey,
361+
session.sessionScope,
362+
),
363+
);
364+
}
360365
}
361366

362367
export function createApprovalSlug(id: string) {
@@ -443,19 +448,23 @@ export function emitExecSystemEvent(
443448
deliveryContext: opts.deliveryContext,
444449
trusted: false,
445450
});
446-
requestHeartbeat(
447-
scopedHeartbeatWakeOptions(
448-
sessionKey,
449-
{
450-
source: "exec-event",
451-
intent: "event",
452-
reason: "exec-event",
453-
coalesceMs: 0,
454-
},
455-
opts.mainKey,
456-
opts.sessionScope,
457-
),
458-
);
451+
// Subagent sessions receive exec results via process poll and announce flow;
452+
// the heartbeat would fall back to the main session and cause spurious wakes.
453+
if (!isSubagentSessionKey(sessionKey)) {
454+
requestHeartbeat(
455+
scopedHeartbeatWakeOptions(
456+
sessionKey,
457+
{
458+
source: "exec-event",
459+
intent: "event",
460+
reason: "exec-event",
461+
coalesceMs: 0,
462+
},
463+
opts.mainKey,
464+
opts.sessionScope,
465+
),
466+
);
467+
}
459468
}
460469

461470
export { renderExecUpdateText } from "./bash-tools.exec-output.js";

0 commit comments

Comments
 (0)