Background Exec and Process Management in OpenClaw
Learn how OpenClaw executes shell commands with the exec tool and manages long-running background sessions using the process tool. Essential for developers automating complex workflows.
Read this when
- Adding or modifying background exec behavior
- Debugging long-running exec tasks
OpenClaw executes shell commands via the exec tool, with long-running operations retained in memory. Oversight of those background sessions falls to the process tool.
exec tool
Parameters:
| Parameter | Description |
|---|---|
command | Mandatory. The shell command to execute. |
workdir | Directory for execution; when omitted, the default cwd applies. |
env | Supplemental environment variables passed to the command. |
yieldMs | Delay in milliseconds before the process is backgrounded, with 10000 as the default. |
background | Forces immediate background execution. |
timeoutSeconds | Timeout measured in seconds, defaulting to tools.exec.timeoutSeconds; upon expiry the process is terminated. Passing timeoutSeconds: 0 turns off the exec process timeout for that invocation. |
pty | Uses a pseudo-terminal when one is available, which suits TTY-dependent CLIs and coding agents. |
elevated | Executes beyond the sandbox when elevated mode is permitted, with gateway as the default, or node when the exec target is node. |
host | Designates the exec target among auto, sandbox, gateway, or node. |
node | Node identifier or name, applied together with host: "node". |
Behavior:
- Directly returned output accompanies foreground runs, and notification occurs when earlier output surpassed the combined cap.
- Once backgrounded, whether explicitly or through the
yieldMstimeout, the tool responds withstatus: "running"plussessionIdalong with a brief output excerpt. - Runs that are backgrounded or use
yieldMspick uptools.exec.timeoutSecondsunless an explicittimeoutSecondsis supplied in the call. - Until the session is polled or cleared, output remains in memory, bounded by the per-session aggregate cap.
- Finished sessions lapse once their configured TTL passes. The registry keeps at most 50 completed sessions and 2,000,000 characters of retained output in total, with the oldest entries removed first. Even when the newest finished session alone exceeds the global limit, its capped per-session aggregate is preserved.
- When the
processtool is blocked,execruns in sync mode and disregardsyieldMsandbackground. - For context-aware shell and profile rules, spawned exec commands are given
OPENCLAW_SHELL=exec. - For work that should begin immediately and run long: launch it once, and if enabled, automatic completion wake handles the finish once the command produces output or errors out.
- When automatic completion wake is not an option, or a quiet-success confirmation is needed for a command that ends cleanly without output, use
processfor polling. - Avoid using
sleeploops or repeated polling to mimic reminders or postponed follow-ups; cron is the right tool for future work.
Env overrides
| Variable | Effect |
|---|---|
OPENCLAW_BASH_YIELD_MS | Default pre-background delay in milliseconds. Defaults to 10000, with a clamp range of 10 to 120000. |
OPENCLAW_BASH_MAX_OUTPUT_CHARS | In-memory aggregate cap measured in characters. Defaults to 200000, clamped between 1000 and 200000. |
OPENCLAW_BASH_PENDING_MAX_OUTPUT_CHARS | Cap on pending stdout/stderr per stream. Defaults to 30000, clamped to 1000-200000 and constrained by the aggregate cap. |
OPENCLAW_BASH_JOB_TTL_MS | TTL for completed sessions in milliseconds, limited to a 1 minute to 3 hour span. |
OPENCLAW_PROCESS_INPUT_WAIT_IDLE_MS | Idle-output threshold that flags writable background sessions as likely awaiting input. Defaults to 15000. |
Config (preferred over env overrides)
| Key | Default | Effect |
|---|---|---|
tools.exec.backgroundMs | 10000 | Equivalent to OPENCLAW_BASH_YIELD_MS. |
tools.exec.timeoutSeconds | 1800 | Timeout applied per call by default. |
tools.exec.cleanupMs | 1800000 | Matches OPENCLAW_BASH_JOB_TTL_MS. |
tools.exec.notifyOnExit | true | Queues a system event and requests a heartbeat when a backgrounded exec terminates. |
tools.exec.notifyOnExitEmptySuccess | false | Also queues completion events for successful backgrounded runs that produce no output. |
Child process bridging
For long-running child processes launched outside the exec/process tools, such as CLI respawns or gateway helpers, attach the child-process bridge helper so termination signals propagate and listeners detach on exit or close. This prevents orphaned processes under systemd and keeps shutdown behavior uniform across platforms.
process tool
Actions:
| Action | Effect |
|---|---|
list | Sessions that are running or have completed. |
poll | Pull fresh output for a session, including its exit code. |
log | Fetch combined output along with input-recovery guidance. Works with offset and limit. |
write | Provide stdin (data, with eof being optional). |
send-keys | Deliver explicit key tokens or raw bytes to a session backed by a PTY. |
submit | Submit Enter or a carriage return to a PTY-backed session. |
paste | Send plain text, optionally using bracketed paste mode. |
kill | Stop a background session. |
clear | Drop a completed session from memory. |
remove | Stop it if active, otherwise wipe it if done. |
Notes:
- Only sessions launched in the background are tracked and retained, and that retention is memory-only, never disk-based. Any process restart wipes them.
- Resetting or deleting a session affects solely its own finished background processes; other sessions, explicit shared scopes, and active processes stay untouched.
- While a background session is alive, cooperative host suspension and a safe Gateway restart are blocked until the process owner confirms the process has truly exited.
process removecan hide a running session right after a termination request; suspension and restart stay blocked until exit confirmation arrives.- Session logs appear in chat history only when you invoke
process poll/logand the tool result gets stored. processis agent-scoped, so it sees only sessions that same agent initiated.- For status, logs, or completion confirmation when automatic completion wake is unavailable, turn to
poll/log. - Before restoring an interactive CLI, run
logso the current transcript, stdin state, and input-wait hint appear together. - When input or intervention is needed, rely on
write/send-keys/submit/paste/kill. process listprovides a derivedname(command verb plus target) to make quick scans easier.process list,poll, andlogsurfacewaitingForInputonly when the session still has writable stdin and has sat idle beyond the input-wait threshold (15000 ms by default,OPENCLAW_PROCESS_INPUT_WAIT_IDLE_MS).process logrelies on line-basedoffset/limit. If neither is given, it returns the last 200 lines with a paging hint. Whenoffsetis present butlimitis absent, it returns fromoffsetto the end, with no 200-line cap.process pollandprocess logtell apart output dropped at the aggregate retention cap from output merely skipped by the pending buffer or retained tail. Dropped output is unrecoverable; paged logs can only examine the retained portion.poll'stimeoutwaits up to that many milliseconds before responding; anything above 30000 gets clamped to 30000.- Polling is meant for on-demand status checks, not as a wait-loop scheduler. For deferred work, use cron.
Examples
Run a lengthy task, then check on it later:
{ "tool": "exec", "command": "sleep 5 && echo done", "yieldMs": 1000 }
{ "tool": "process", "action": "poll", "sessionId": "<id>" }
Look at an interactive session before sending anything:
{ "tool": "process", "action": "log", "sessionId": "<id>" }
Launch directly in the background:
{ "tool": "exec", "command": "npm run build", "background": true }
Feed stdin:
{ "tool": "process", "action": "write", "sessionId": "<id>", "data": "y\n" }
Send PTY keys:
{ "tool": "process", "action": "send-keys", "sessionId": "<id>", "keys": ["C-c"] }
Submit the current line:
{ "tool": "process", "action": "submit", "sessionId": "<id>" }
Paste literal text:
{ "tool": "process", "action": "paste", "sessionId": "<id>", "text": "line1\nline2\n" }