openclaw - 💡(How to fix) Fix [Bug]: Unhandled promise rejection in pi-agent-core crashes gateway when exec preflight refuses a command mid-run [1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#69836Fetched 2026-04-22 07:47:45
View on GitHub
Comments
1
Participants
2
Timeline
3
Reactions
0
Author
Participants
Timeline (top)
labeled ×2commented ×1

The gateway process exits with code 1 when the exec tool's preflight validation refuses a command during an active agent run, due to an unhandled promise rejection in pi-agent-core's processEvents() from a stale stdout callback fired after run teardown.

Error Message

Gateway crash log:

[tools] exec failed: exec preflight: complex interpreter invocation detected; refusing to run without script preflight validation[openclaw] Unhandled promise rejection: Error: Agent listener invoked outside active run at Agent.processEvents (@mariozechner/pi-agent-core/src/agent.ts:533:10) at @mariozechner/pi-agent-core/src/agent.ts:380:21 at Object.onUpdate (@mariozechner/pi-agent-core/src/agent-loop.ts:539:7) at emitUpdate (openclaw/dist/exec-defaults-uj0McX2k.js:1524:8)[WARN] OpenClaw exited with code 1

Line numbers are from the compiled dist. Source equivalents in badlogic/pi-mono are packages/agent/src/agent.ts (processEvents) and packages/agent/src/agent-loop.ts (executePreparedToolCall).

Stopgap patch applied locally to unblock the gateway:

File: /data/.npm-global/lib/node_modules/openclaw/node_modules/@mariozechner/pi-agent-core/dist/agent.js

 const signal = this.activeRun?.abortController.signal;
 if (!signal) {


   throw new Error("Agent listener invoked outside active run");




   // Stopgap: run already torn down, stale async callback from an


   // in-flight process stdout/stderr handler (e.g. exec preflight


   // refused mid-run, child process continued emitting output after


   // finishRun() cleared activeRun). Safe to drop: no listeners to


   // notify. Upstream fix should ensure child processes are aborted


   // on run teardown so this path is never reached.


   console.debug(


       `[pi-agent-core] processEvents: dropping stale "${event.type}" event, no active run`,


   );


   return;

}

Original preserved as agent.js.bak.

Root Cause

The gateway process exits with code 1 when the exec tool's preflight validation refuses a command during an active agent run, due to an unhandled promise rejection in pi-agent-core's processEvents() from a stale stdout callback fired after run teardown.

Fix Action

Fix / Workaround

Stopgap patch applied locally to unblock the gateway:

Code Example

Gateway crash log:

[tools] exec failed: exec preflight: complex interpreter invocation detected; refusing to run without script preflight validation[openclaw] Unhandled promise rejection: Error: Agent listener invoked outside active run
at Agent.processEvents (@mariozechner/pi-agent-core/src/agent.ts:533:10)
at @mariozechner/pi-agent-core/src/agent.ts:380:21
at Object.onUpdate (@mariozechner/pi-agent-core/src/agent-loop.ts:539:7)
at emitUpdate (openclaw/dist/exec-defaults-uj0McX2k.js:1524:8)[WARN] OpenClaw exited with code 1

Line numbers are from the compiled dist. Source equivalents in `badlogic/pi-mono` are `packages/agent/src/agent.ts` (`processEvents`) and `packages/agent/src/agent-loop.ts` (`executePreparedToolCall`).

Stopgap patch applied locally to unblock the gateway:

File: `/data/.npm-global/lib/node_modules/openclaw/node_modules/@mariozechner/pi-agent-core/dist/agent.js`

     const signal = this.activeRun?.abortController.signal;
     if (!signal) {


       throw new Error("Agent listener invoked outside active run");




       // Stopgap: run already torn down, stale async callback from an


       // in-flight process stdout/stderr handler (e.g. exec preflight


       // refused mid-run, child process continued emitting output after


       // finishRun() cleared activeRun). Safe to drop: no listeners to


       // notify. Upstream fix should ensure child processes are aborted


       // on run teardown so this path is never reached.


       console.debug(


           `[pi-agent-core] processEvents: dropping stale "${event.type}" event, no active run`,


       );


       return;
   }



Original preserved as `agent.js.bak`.
RAW_BUFFERClick to expand / collapse

Bug type

Crash (process/app exits or hangs)

Beta release blocker

No

Summary

The gateway process exits with code 1 when the exec tool's preflight validation refuses a command during an active agent run, due to an unhandled promise rejection in pi-agent-core's processEvents() from a stale stdout callback fired after run teardown.

Steps to reproduce

  1. Start OpenClaw gateway 2026.4.5 inside the hvps-openclaw Docker container on Ubuntu 24.04.
  2. Via any connected channel (Telegram in my case), prompt the agent to run a shell command that will trigger exec preflight refusal, e.g. ask it to write a script using a heredoc: "write /tmp/foo.py using cat > /tmp/foo.py << EOF".
  3. Observe the gateway log: [tools] exec failed: exec preflight: complex interpreter invocation detected; refusing to run.
  4. Wait 2 to 5 seconds.
  5. Gateway exits with [openclaw] Unhandled promise rejection: Error: Agent listener invoked outside active run followed by [WARN] OpenClaw exited with code 1. The container stays running but the gateway process is dead until a manual docker restart.

Expected behavior

The exec preflight refusal should be returned to the agent as a tool error. The agent run should end cleanly (with or without a fallback attempt) and the gateway process should remain alive. Any stale stdout or stderr callback from the spawned child process firing after run teardown should be handled gracefully rather than propagating as an unhandled promise rejection that terminates the gateway.

Actual behavior

The preflight refusal is caught and handleRunFailure -> finishRun() clears this.activeRun. The child process spawned by the exec tool continues emitting stdout or stderr, which fires onStdout/onStderr in the exec supervisor. These call emitUpdate() -> onUpdate() -> Agent.processEvents(). processEvents() then checks this.activeRun?.abortController.signal, finds it undefined, and throws new Error("Agent listener invoked outside active run"). The throw is not caught; Node's unhandledRejection handler fires and the gateway exits with code 1.

Stack trace from the crash:

[openclaw] Unhandled promise rejection: Error: Agent listener invoked outside active run at Agent.processEvents (@mariozechner/pi-agent-core/src/agent.ts:533:10) at @mariozechner/pi-agent-core/src/agent.ts:380:21 at Object.onUpdate (@mariozechner/pi-agent-core/src/agent-loop.ts:539:7) at emitUpdate (openclaw/dist/exec-defaults-uj0McX2k.js:1524:8)

Note: line numbers are from the compiled dist. Source equivalents are in badlogic/pi-mono at packages/agent/src/agent.ts (processEvents) and packages/agent/src/agent-loop.ts (executePreparedToolCall).

Recovery requires docker restart <container>; there is no automatic retry from the supervisor.

OpenClaw version

2026.4.5 (3e72c03)

Operating system

Ubuntu 24.04 (inside Docker container on Hostinger VPS)

Install method

docker (ghcr.io/hostinger/hvps-openclaw:latest via Hostinger one-click)

Model

anthropic/claude-sonnet-4-6

Provider / routing chain

openclaw -> anthropic API (direct)

Additional provider/model setup details

No response

Logs, screenshots, and evidence

Gateway crash log:

[tools] exec failed: exec preflight: complex interpreter invocation detected; refusing to run without script preflight validation[openclaw] Unhandled promise rejection: Error: Agent listener invoked outside active run
at Agent.processEvents (@mariozechner/pi-agent-core/src/agent.ts:533:10)
at @mariozechner/pi-agent-core/src/agent.ts:380:21
at Object.onUpdate (@mariozechner/pi-agent-core/src/agent-loop.ts:539:7)
at emitUpdate (openclaw/dist/exec-defaults-uj0McX2k.js:1524:8)[WARN] OpenClaw exited with code 1

Line numbers are from the compiled dist. Source equivalents in `badlogic/pi-mono` are `packages/agent/src/agent.ts` (`processEvents`) and `packages/agent/src/agent-loop.ts` (`executePreparedToolCall`).

Stopgap patch applied locally to unblock the gateway:

File: `/data/.npm-global/lib/node_modules/openclaw/node_modules/@mariozechner/pi-agent-core/dist/agent.js`

     const signal = this.activeRun?.abortController.signal;
     if (!signal) {


       throw new Error("Agent listener invoked outside active run");




       // Stopgap: run already torn down, stale async callback from an


       // in-flight process stdout/stderr handler (e.g. exec preflight


       // refused mid-run, child process continued emitting output after


       // finishRun() cleared activeRun). Safe to drop: no listeners to


       // notify. Upstream fix should ensure child processes are aborted


       // on run teardown so this path is never reached.


       console.debug(


           `[pi-agent-core] processEvents: dropping stale "${event.type}" event, no active run`,


       );


       return;
   }



Original preserved as `agent.js.bak`.

Impact and severity

  • Affected users/systems: anyone running OpenClaw who uses the built-in exec tool via an agent run. The exec preflight is a common refusal path, so any heredoc, complex shell construction, or interpreter invocation that trips preflight will crash the gateway.
  • Severity: high. A tool-level refusal should never kill the entire gateway. Current behaviour takes the whole process down, disconnecting all channels (Telegram, Discord, etc.) until a manual container restart.
  • Frequency: reliably reproducible on demand. 100% reproduction rate when preflight refuses during an active run that has already spawned the child process.
  • Consequence: gateway offline until manual intervention. All channels disconnect. No automatic retry or recovery.

Additional information

Diagnosis (hypothesis, based on reading compiled dist and tracing the call path):

  1. The exec tool spawns a child process via the supervisor and registers onStdout/onStderr callbacks (handleStdout, handleStderr in exec-defaults-uj0McX2k.js). Each callback calls emitUpdate(), which calls opts.onUpdate(), where onUpdate is the callback passed in from pi-agent-core's executePreparedToolCall.

  2. The exec preflight throws mid-run (or the run is otherwise aborted). runWithLifecycle catches the error, calls handleRunFailure, then finishRun(), which sets this.activeRun = undefined.

  3. The child process continues emitting stdout/stderr after finishRun() completes (the process has not been killed). The supervisor fires onStdout, which calls emitUpdate(), then onUpdate(), then emit({ type: "tool_execution_update", ... }), then Agent.processEvents().

  4. processEvents() checks this.activeRun?.abortController.signal. activeRun is undefined. It throws new Error("Agent listener invoked outside active run"). This rejection is not caught anywhere. Node's unhandledRejection handler fires and kills the process.

Open question for maintainers:

Are child processes supposed to be aborted when a run tears down?

  • If yes: there is a supervisor leak in OpenClaw's exec runtime. The child process should receive a kill or abort signal when finishRun() runs or when the run's AbortController fires. The onStdout/onStderr handlers should stop firing after that point. The pi-agent-core guard would then be defence-in-depth rather than the primary fix.
  • If no (child processes intentionally outlive the run, e.g. for backgrounded exec): then processEvents() in pi-agent-core must tolerate stale callbacks, and a guard-return at that level is the right fix.

Either way, an unguarded throw in processEvents() that kills the entire gateway process is the wrong behaviour. A stale callback should never be fatal.

Bundled pi-agent-core version: 0.67.68 (as shipped with OpenClaw 2026.4.5).

extent analysis

TL;DR

The gateway process exits due to an unhandled promise rejection caused by a stale stdout callback from a child process after run teardown, and a potential fix involves modifying the processEvents() function in pi-agent-core to handle such stale callbacks.

Guidance

  • Identify the root cause of the unhandled promise rejection in processEvents() and determine if child processes should be aborted when a run tears down.
  • Consider adding a guard clause in processEvents() to handle stale callbacks and prevent the unhandled promise rejection.
  • Review the supervisor's behavior in OpenClaw's exec runtime to ensure child processes are properly terminated or aborted when a run is finished.
  • Investigate the finishRun() function to ensure it properly cleans up resources and prevents further callbacks from being fired.

Example

A potential fix could involve modifying the processEvents() function to include a guard clause, such as:

if (!this.activeRun?.abortController.signal) {
  console.debug(`[pi-agent-core] processEvents: dropping stale "${event.type}" event, no active run`);
  return;
}

This would prevent the unhandled promise rejection and allow the gateway process to continue running.

Notes

The provided stopgap patch applied locally to agent.js may be a temporary solution, but a more robust fix should be implemented to handle stale callbacks and ensure proper cleanup of resources.

Recommendation

Apply a workaround by modifying the processEvents() function to handle stale callbacks, as the provided stopgap patch suggests, until a more permanent fix can be implemented.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

The exec preflight refusal should be returned to the agent as a tool error. The agent run should end cleanly (with or without a fallback attempt) and the gateway process should remain alive. Any stale stdout or stderr callback from the spawned child process firing after run teardown should be handled gracefully rather than propagating as an unhandled promise rejection that terminates the gateway.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - 💡(How to fix) Fix [Bug]: Unhandled promise rejection in pi-agent-core crashes gateway when exec preflight refuses a command mid-run [1 comments, 2 participants]