mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-13 06:03:39 -06:00
bd897a6143
* fix: exec timeout field does not state its unit, so callers pass milliseconds
`exec.timeout` is in seconds, but its sibling `yieldMs` is in milliseconds and
the `process` tool's identically named `timeout` is also in milliseconds. Those
two tools are used together in one workflow: exec backgrounds a long command,
then process polls it.
The unit exists only in the field description, and code mode deliberately defers
descriptions - the model sees `timeout?: number` and nothing else. A caller that
guesses milliseconds passes 900000 intending 15 minutes and gets ~10 days, so
the command never times out.
Add `timeoutSeconds` as the canonical field, matching the convention already
used by the config default (`tools.exec.timeoutSeconds`) and the sibling
(`yieldMs`). Keep `timeout` as a deprecated alias; `timeoutSeconds` wins when
both are supplied. Project the new field onto `nodeExecSchema`, which
hand-picks its properties.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XQojYRCUdXhcKFeu4QW5Gh
* fix(ci): satisfy test-type and dependency checks
Two CI failures from the test approach, not the change itself.
- check-test-types: TypeBox's TOptional does not surface `description` on its
static type, so the schema assertions failed under tsgo. Read it through a
narrow accessor instead.
- check-dependencies: knip flagged resolveExecTimeoutSeconds as an unused
export because only the test imported it. Move it beside the schema it
resolves, where the exec runtime imports it, so it is production-reachable.
Verified with the same commands CI runs: `run-tsgo.mjs -p
test/tsconfig/tsconfig.core.test.json` reports no errors in these files, and
knip --production reports no unused exports.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XQojYRCUdXhcKFeu4QW5Gh
* fix(ci): fit the exec description budget and satisfy oxlint
- `agent-tools.schema.test.ts` enforces a 550-char budget on the exec tool's
model-facing descriptions; the two new ones pushed it to 640. Shortened both.
The rename is what makes this possible: the unit now lives in the field name,
so the description no longer has to carry it. Extended that budget test to
pin `timeoutSeconds` alongside `timeout`.
- oxlint `no-base-to-string`: the test's description accessor typed the field as
`unknown` and stringified it. Typed as `string | undefined` instead.
Verified: budget test passes (137 tests green across both suites), and all six
behavior tests still fail against origin/main.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XQojYRCUdXhcKFeu4QW5Gh
* fix: remove the unit-ambiguous exec timeout field instead of deprecating it
Owner decision on the review's "ambiguous model-visible alias" finding: drop
`timeout` entirely rather than keep it as a deprecated alias.
Keeping it visible defeated the purpose. Code mode renders property names and
types and defers descriptions, so a model would still be offered a bare
`timeout` sitting next to a millisecond-based `yieldMs` and could still pick it.
A deprecation marker in a description the model never sees changes nothing.
Backward compatibility is not required here: every tool call is constructed
fresh by the model from the current schema, and a repository search found no
internal caller passing `timeout` to the exec tool. `timeoutSeconds` is now the
only exec timeout field, on both the main and node-only surfaces.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XQojYRCUdXhcKFeu4QW5Gh
* fix: migrate the internal exec callers to timeoutSeconds
Removing `timeout` from the exec schema broke three real internal callers that
the earlier survey missed, plus two test files and the runtime's own reads.
`check-test-types` caught it; my local search had filtered `timeout:` matches to
lines mentioning "exec" and discarded these.
- src/auto-reply/reply/bash-command.ts
- src/auto-reply/reply/commands-diagnostics.ts
- src/auto-reply/reply/commands-export-trajectory.ts
all passed `timeout: timeoutSec` and now pass `timeoutSeconds: timeoutSec`.
- bash-tools.exec-run.ts reads the field through the same typeof narrowing the
third call site already used; `params` is loosely typed, so a raw read is
`unknown`.
- Test fixtures updated. A vitest `waitFor({ timeout })` option in
background-abort.test.ts is NOT an exec param and was left alone.
`run-tsgo.mjs -p test/tsconfig/tsconfig.core.test.json` now reports zero errors
in changed files; the 5 remaining `rejectSymlinks` errors are pre-existing on
main. 148 tests pass across the affected suites.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XQojYRCUdXhcKFeu4QW5Gh
* docs: synchronize the exec config table with timeoutSeconds
Addresses the P2 finding. The tools.exec.timeoutSeconds row still described the
per-call override as `timeout` and `timeout: 0`, which no longer exist. The
only remaining bare `timeout` reference in this page is the deliberate
contrast with the process tool's millisecond field.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XQojYRCUdXhcKFeu4QW5Gh
* fix: migrate the QA lifecycle caller and the shared ExecToolArgs contract
Two findings from the re-review of 6e8c3560e7, both real.
[P1] test/e2e/qa-lab/runtime/openclaw-exec-process-lifecycle.e2e.test.ts:174
still passed `timeout: 0.05`. After the removal that field is accepted and
ignored, so the call silently used the default timeout and could not produce
the asserted 50 ms `overall-timeout` result. Migrated.
[P2] ExecToolArgs in bash-tools.exec-request-preparation.ts:33 still declared
`timeout?: number` while the runtime reads only `timeoutSeconds`, offering
request-preparation users an accepted-but-inert argument. Renamed.
Why the typecheck missed both: ExecToolArgs is
`Record<string, unknown> & {...}`, and that index signature defeats excess
property checking, so a stale member produces no error at any call site. The
e2e file also sits outside the core test tsconfig I had been running locally.
Left alone deliberately: `processTool.execute({ action: "poll", timeout })` is
milliseconds and correct, and `createBashTool` in sessions/tools/bash.ts is a
separate tool surface with its own timeout.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XQojYRCUdXhcKFeu4QW5Gh
* fix(test): update the node exec tool property assertion to timeoutSeconds
src/gateway/tool-resolution.exclude.test.ts:413 asserts the exact property list
of the node-forced exec tool and still expected "timeout". Renamed to match
nodeExecSchema.
Found by checks-node-compact-large-5; my local core-test tsconfig does not cover
src/gateway, so it did not surface there.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XQojYRCUdXhcKFeu4QW5Gh
* fix: accept the legacy exec timeout as an undocumented alias
Removing the field from the schema does not reject it at runtime: the exec
schema accepts unknown properties, so a call built against an older schema was
accepted and then silently ignored, taking the default deadline instead of the
one it asked for.
Route every read through resolveExecTimeoutSeconds, which prefers the canonical
timeoutSeconds and falls back to a numeric timeout. The alias stays out of
execSchema, nodeExecSchema and ExecToolArgs, so no new caller can adopt it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XQojYRCUdXhcKFeu4QW5Gh
* docs: update the background-process guide to timeoutSeconds
The exec parameter table and the inheritance note still taught `timeout` and
`timeout: 0`, which kept the retired spelling publicly discoverable and
contradicted the exec guide.
`poll`'s own millisecond `timeout` is left alone; it is a different tool and a
different unit, which is the collision this rename removes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XQojYRCUdXhcKFeu4QW5Gh
* fix(exec): reject removed timeout field
* chore: regenerate plugin SDK API baseline
---------
Co-authored-by: Marvinthebored <262704729+Marvinthebored@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
10 KiB
10 KiB
summary, read_when, title
| summary | read_when | title | ||
|---|---|---|---|---|
| Background exec execution and process management |
|
Background exec and process tool |
OpenClaw runs shell commands through the exec tool and keeps long-running tasks in memory. The process tool manages those background sessions.
exec tool
Parameters:
| Parameter | Description |
|---|---|
command |
Required. Shell command to run. |
workdir |
Working directory; omit to use the default cwd. |
env |
Extra environment variables for the command. |
yieldMs |
Milliseconds to wait before backgrounding (default 10000). |
background |
Run in background immediately. |
timeoutSeconds |
Timeout in seconds (default tools.exec.timeoutSeconds); kills the process on expiry. Set timeoutSeconds: 0 to disable the exec process timeout for that call. |
pty |
Run in a pseudo-terminal when available (TTY-required CLIs, coding agents). |
elevated |
Run outside the sandbox if elevated mode is enabled/allowed (gateway by default, or node when the exec target is node). |
host |
Exec target: auto, sandbox, gateway, or node. |
node |
Node id/name, used with host: "node". |
Behavior:
- Foreground runs return retained output directly and disclose when earlier output exceeded the aggregate cap.
- When backgrounded (explicit or via
yieldMstimeout), the tool returnsstatus: "running"+sessionIdand a short output tail. - Backgrounded and
yieldMsruns inherittools.exec.timeoutSecondsunless the call passes an explicittimeoutSeconds. - Output stays in memory up to the per-session aggregate cap until the session is polled or cleared.
- Finished sessions expire after their configured TTL. The registry also retains at most 50 finished sessions and 2,000,000 total retained output characters, evicting the oldest records first. The newest completed session retains its capped per-session aggregate even when that record alone exceeds the global limit.
- If the
processtool is disallowed,execruns synchronously and ignoresyieldMs/background. - Spawned exec commands receive
OPENCLAW_SHELL=execfor context-aware shell/profile rules. - For long-running work that starts now: start it once and rely on automatic completion wake (when enabled) once the command emits output or fails.
- If automatic completion wake is unavailable, or you need quiet-success confirmation for a command that exits cleanly with no output, poll with
process. - Don't emulate reminders or delayed follow-ups with
sleeploops or repeated polling — use cron for future work.
Env overrides
| Variable | Effect |
|---|---|
OPENCLAW_BASH_YIELD_MS |
Default yield before backgrounding (ms). Default 10000, clamped 10-120000. |
OPENCLAW_BASH_MAX_OUTPUT_CHARS |
In-memory aggregate cap in characters. Default 200000, clamped 1000-200000. |
OPENCLAW_BASH_PENDING_MAX_OUTPUT_CHARS |
Pending stdout/stderr cap per stream. Default 30000, clamped 1000-200000 and limited by the aggregate cap. |
OPENCLAW_BASH_JOB_TTL_MS |
TTL for finished sessions (ms), bounded to 1m-3h. |
OPENCLAW_PROCESS_INPUT_WAIT_IDLE_MS |
Idle-output threshold before writable background sessions are marked as likely waiting for input. Default 15000. |
Config (preferred over env overrides)
| Key | Default | Effect |
|---|---|---|
tools.exec.backgroundMs |
10000 | Same as OPENCLAW_BASH_YIELD_MS. |
tools.exec.timeoutSeconds |
1800 | Default per-call timeout. |
tools.exec.cleanupMs |
1800000 | Same as OPENCLAW_BASH_JOB_TTL_MS. |
tools.exec.notifyOnExit |
true | Enqueue a system event + request heartbeat when a backgrounded exec exits. |
tools.exec.notifyOnExitEmptySuccess |
false | Also enqueue completion events for successful backgrounded runs with no output. |
Child process bridging
When spawning long-running child processes outside the exec/process tools (CLI respawns, gateway helpers), attach the child-process bridge helper so termination signals forward and listeners detach on exit/error. This avoids orphaned processes on systemd and keeps shutdown consistent across platforms.
process tool
Actions:
| Action | Effect |
|---|---|
list |
Running + finished sessions. |
poll |
Drain new output for a session (also reports exit status). |
log |
Read aggregated output and input-recovery hints. Supports offset + limit. |
write |
Send stdin (data, optional eof). |
send-keys |
Send explicit key tokens or bytes to a PTY-backed session. |
submit |
Send Enter/carriage return to a PTY-backed session. |
paste |
Send literal text, optionally wrapped in bracketed paste mode. |
kill |
Terminate a background session. |
clear |
Remove a finished session from memory. |
remove |
Kill if running, otherwise clear if finished. |
Notes:
- Only backgrounded sessions are listed/persisted — in memory only, not on disk. Sessions are lost on process restart.
- Resetting or deleting a session clears only its completed background processes; other sessions, explicit shared scopes, and running processes remain unaffected.
- A live background session blocks cooperative host suspension and safe Gateway restart until the process owner confirms its actual exit.
process removecan hide a running session immediately after requesting termination; suspension and restart remain blocked until exit confirmation.- Session logs are only saved to chat history if you run
process poll/logand the tool result is recorded. processis scoped per agent; it only sees sessions started by that agent.- Use
poll/logfor status, logs, or completion confirmation when automatic completion wake is unavailable. - Use
logbefore recovering an interactive CLI, so the current transcript, stdin state, and input-wait hint are visible together. - Use
write/send-keys/submit/paste/killwhen you need input or intervention. process listincludes a derivedname(command verb + target) for quick scans.process list,poll, andlogreportwaitingForInputonly when the session still has writable stdin and has been idle longer than the input-wait threshold (default 15000 ms,OPENCLAW_PROCESS_INPUT_WAIT_IDLE_MS).process loguses line-basedoffset/limit. When both are omitted, it returns the last 200 lines with a paging hint. Whenoffsetis set andlimitisn't, it returns fromoffsetto the end (not capped to 200).process pollandprocess logdistinguish output discarded at the aggregate retention cap from output merely omitted by the pending buffer or retained tail. Discarded output cannot be recovered; paged logs can inspect only the retained portion.poll'stimeoutwaits up to that many milliseconds before returning; values above 30000 are clamped to 30000.- Polling is for on-demand status, not wait-loop scheduling. If the work should happen later, use cron.
Examples
Run a long task and poll later:
{ "tool": "exec", "command": "sleep 5 && echo done", "yieldMs": 1000 }
{ "tool": "process", "action": "poll", "sessionId": "<id>" }
Inspect an interactive session before sending input:
{ "tool": "process", "action": "log", "sessionId": "<id>" }
Start immediately in background:
{ "tool": "exec", "command": "npm run build", "background": true }
Send stdin:
{ "tool": "process", "action": "write", "sessionId": "<id>", "data": "y\n" }
Send PTY keys:
{ "tool": "process", "action": "send-keys", "sessionId": "<id>", "keys": ["C-c"] }
Submit current line:
{ "tool": "process", "action": "submit", "sessionId": "<id>" }
Paste literal text:
{ "tool": "process", "action": "paste", "sessionId": "<id>", "text": "line1\nline2\n" }