mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 09:41:11 +00:00
fix: stop emitting post-background exec updates (#61627) (thanks @openperf)
* fix(exec ): stop emitting tool updates after session is backgrounded When an exec session is backgrounded (background: true), the owning agent run resolves its tool-call promise and may finish. The stdout handler's emitUpdate() closure, however, kept invoking opts.onUpdate(), delivering tool_execution_update events to a listener whose active run had already ended. This surfaced as an unhandled rejection and crashed the gateway process. Guard emitUpdate() with a session.backgrounded || session.exited check so that post-background output is still captured via appendOutput() but no longer forwarded to the (now-stale) agent-loop callback. Fixes #61592 * style: trim exec backgrounding comments * fix: stop emitting post-background exec updates (#61627) (thanks @openperf) * fix: place exec changelog entry at end of fixes (#61627) (thanks @openperf) --------- Co-authored-by: Ayaan Zaidi <hi@obviy.us>
This commit is contained in:
@@ -242,6 +242,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Matrix: keep direct transport requests on the pinned dispatcher by routing them through undici runtime fetch, so Matrix clients resume syncing on newer runtimes without dropping the validated address binding. (#61595) Thanks @gumadeiras.
|
||||
- Plugins/facades: resolve globally installed bundled-plugin runtime facades from registry roots so bundled channels like LINE still boot when the winning plugin install lives under the global extensions directory with an encoded scoped folder name. (#61297) Thanks @openperf.
|
||||
- Matrix: avoid failing startup when token auth already knows the user ID but still needs optional device metadata, retry transient auth bootstrap requests, and backfill missing device IDs after startup while keeping unknown-device storage reuse conservative until metadata is repaired. (#61383) Thanks @gumadeiras.
|
||||
- Agents/exec: stop streaming `tool_execution_update` events after an exec session backgrounds, preventing delayed background output from hitting a stale listener and crashing the gateway while keeping the output available through `process poll/log`. (#61627) Thanks @openperf.
|
||||
|
||||
## 2026.4.2
|
||||
|
||||
|
||||
@@ -588,6 +588,9 @@ export async function runExecProcess(opts: {
|
||||
if (!opts.onUpdate) {
|
||||
return;
|
||||
}
|
||||
if (session.backgrounded || session.exited) {
|
||||
return;
|
||||
}
|
||||
const tailText = session.tail || session.aggregated;
|
||||
const warningText = opts.warnings.length ? `${opts.warnings.join("\n")}\n\n` : "";
|
||||
opts.onUpdate({
|
||||
|
||||
@@ -679,3 +679,34 @@ describe("applyPathPrepend with case-insensitive PATH key", () => {
|
||||
expect("Path" in env).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("exec backgrounded onUpdate suppression", () => {
|
||||
useCapturedEnv([...SHELL_ENV_KEYS], applyDefaultShellEnv);
|
||||
|
||||
it(
|
||||
"does not invoke onUpdate after the session is backgrounded",
|
||||
async () => {
|
||||
const onUpdateSpy = vi.fn();
|
||||
const tool = createTestExecTool({ allowBackground: true, backgroundMs: 0 });
|
||||
const command = joinCommands([shellEcho("before"), yieldDelayCmd, shellEcho("after")]);
|
||||
const result = await tool.execute(
|
||||
nextCallId(),
|
||||
{ command, background: true },
|
||||
undefined,
|
||||
onUpdateSpy,
|
||||
);
|
||||
|
||||
expect(readProcessStatus(result.details)).toBe(PROCESS_STATUS_RUNNING);
|
||||
const sessionId = requireSessionId(result.details as { sessionId?: string });
|
||||
const callsBeforeBackground = onUpdateSpy.mock.calls.length;
|
||||
await expect
|
||||
.poll(() => {
|
||||
const finished = getFinishedSession(sessionId);
|
||||
return Boolean(finished);
|
||||
}, BACKGROUND_POLL_OPTIONS)
|
||||
.toBe(true);
|
||||
expect(onUpdateSpy.mock.calls.length).toBe(callsBeforeBackground);
|
||||
},
|
||||
isWin ? 15_000 : 5_000,
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user