Skip to content

refactor(process): route bounded commands through Execa#106495

Merged
steipete merged 19 commits into
mainfrom
codex/execa-process-sweep
Jul 13, 2026
Merged

refactor(process): route bounded commands through Execa#106495
steipete merged 19 commits into
mainfrom
codex/execa-process-sweep

Conversation

@steipete

@steipete steipete commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Related: #105920
Follow-up to #105939

AI-assisted. I understand the execution, cancellation, output, and platform behavior changed here.

What Problem This Solves

Resolves a problem where bounded one-shot commands across core and bundled plugins still bypassed OpenClaw's canonical Execa-backed execution layer, leaving inconsistent timeout, environment, output-cap, error, and process-tree behavior.

Why This Change Was Made

This routes suitable bounded command sites through the canonical process wrapper, adds a buffered SDK seam for plugin-owned callers, and splits the execution implementation into focused spawn, output, result, termination, and runner modules behind the existing src/process/exec.ts facade. OpenClaw-specific contracts remain owned by the wrapper: argv-only Windows batch safety, console-codepage decoding, capped capture, abort/timeout semantics, inherited-pipe draining, and process-tree termination. Specialized interactive, streaming, supervised, daemon, tunnel, watcher, IPC, and detached-restart processes remain direct spawns by design.

The Windows termination path now waits for bounded taskkill ownership to settle before aborting Execa, avoiding a live-root PID reuse race.

Docker and SSH sandbox commands use one explicit 100 MiB output cap, so their full-buffer contract neither inherits Execa's hidden default nor permits unbounded gateway heap growth.

QA Lab node probing passes restricted environments as the exact base environment, preserving the old execFile({ env }) isolation contract.

User Impact

No intended user-visible behavior change. Operators and plugin developers get more consistent bounded-command behavior, while long-lived and interactive process owners keep their specialized lifecycle paths.

Evidence

  • AWS Crabbox run_ac5c7b9ee846: pnpm check:changed and pnpm build passed, including LOC, formatting, dependency guards, SDK API/surface checks, five TypeScript lanes, core and extension lint, import-cycle checks, runtime guards, core bundle, SDK declarations, Control UI build, and performance budgets.
  • AWS Crabbox run_11269723e625: focused src/process plus src/node-host/invoke.run-command.test.ts proof passed 211 tests with 1 platform skip across three Vitest shards, including Windows invocation, timeout, output-cap, descendant-pipe, and command-tree termination coverage.
  • Blacksmith Testbox tbx_01kxe2ktcvyghmnpdx3f4k821x / Actions 29263749854: final integration check:changed passed core and core-test typechecks plus guards; focused process, worker-tunnel, and node-host proof passed 222 tests with 1 platform skip across four Vitest shards.
  • Blacksmith Testbox tbx_01kxe3fzzq3razeqqkdjfteyyf / Actions 29264821473: sandbox-cap check:changed passed; focused Docker and SSH sandbox proof passed 50 tests.
  • Blacksmith Testbox tbx_01kxe3xd4c5b6nhjy2aqf25zx6 / Actions 29265336998: QA Lab extension and test typechecks, lint, guards, and 6 focused node-probe tests passed.
  • Fresh autoreview passes were clean after the module split, rebase integration, binary-input narrowing, finite sandbox-cap fixes, and exact QA node-probe environment fix.
  • git diff --check origin/main...HEAD passed.
  • Final reviewed head: 51442e188c9. The diff removes more duplicated child-process lifecycle code than it adds across more than 90 files.

No screenshots: runtime-only process refactor.

@steipete
steipete requested a review from a team as a code owner July 13, 2026 15:27
@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation channel: imessage Channel integration: imessage channel: matrix Channel integration: matrix channel: voice-call Channel integration: voice-call gateway Gateway runtime cli CLI command changes scripts Repository scripts commands Command implementations agents Agent runtime and tooling extensions: acpx extensions: qa-lab extensions: memory-wiki extensions: codex extensions: tts-local-cli plugin: file-transfer extensions: ollama plugin: logbook Logbook automatic work journal plugin extensions: onepassword 1Password plugin plugin: mxc size: XL maintainer Maintainer-authored PR labels Jul 13, 2026
@steipete steipete self-assigned this Jul 13, 2026
@steipete
steipete force-pushed the codex/execa-process-sweep branch from 727c1ec to 90414ac Compare July 13, 2026 15:38

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3879c6ce7c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/agents/sandbox/docker.ts
Comment thread src/agents/sandbox/ssh.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5c5ad4bca1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread extensions/qa-lab/src/node-exec.ts Outdated
@steipete

steipete commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

Land-ready proof for final head f7948f98ba4:

  • AWS Crabbox run_ac5c7b9ee846: pnpm check:changed && pnpm build passed.
  • AWS Crabbox run_11269723e625: focused src/process and src/node-host/invoke.run-command.test.ts proof passed 211 tests, with 1 platform skip.
  • Testbox tbx_01kxe2ktcvyghmnpdx3f4k821x / Actions 29263749854: pnpm check:changed plus process, worker-tunnel, and node-host proof passed 222 tests, with 1 platform skip.
  • Testbox tbx_01kxe3fzzq3razeqqkdjfteyyf / Actions 29264821473: sandbox cap changed gate plus Docker/SSH proof passed 50 tests.
  • Testbox tbx_01kxe3xd4c5b6nhjy2aqf25zx6 / Actions 29265336998: QA Lab extension/test typechecks, lint, guards, and 6 focused node-probe tests passed.
  • Testbox tbx_01kxe5p8hf4h7edtqg5ga1a7a6 / Actions 29267450204: final process, update CLI, and systemd proof passed 314 tests with 1 platform skip, followed by green pnpm check:changed.
  • Testbox tbx_01kxe6zjcryna9gb4hwfn5tjqw / Actions 29268980764: post-main integration WhatsApp proof passed 77 tests, followed by green pnpm check:changed.
  • AWS Crabbox run_10d037540f93: post-exit timeout-race proof passed 57 tests with 1 platform skip.
  • Testbox tbx_01kxe8pf9hme5gcvwpptdaaczc / Actions 29270957898: final process, Windows, and inherited-pipe proof passed 60 tests with 1 platform skip.
  • Testbox Actions 29271697445 and 29272510040: final migrated-mock proof passed 9 and 14 tests.
  • Fresh autoreview is clean after the final rebase conflict resolution restored the Execa tar path in the split WhatsApp setup owner. All hosted review findings were fixed and resolved. git diff --check is clean.

Known proof gap: no live Windows host in the final pass. Dedicated Windows invocation, codepage, batch-command, and taskkill behavior remains covered by focused tests and hosted Windows CI from the reviewed pre-rebase head.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fd8320e8d3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +666 to 669
result = await runCommandWithTimeout([secureCommandPath, ...(policy.exec.args ?? [])], {
baseEnv: {},
cwd,
env: childEnv,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve exact policy command environments

When security.installPolicy.exec is configured with env/passEnv, this new runCommandWithTimeout path goes through spawnCommand, whose resolveCommandEnv() always calls markOpenClawExecEnv() and injects OPENCLAW_CLI=1. That violates the policy surface documented in docs/tools/skills-config.md where only literal env entries and named passEnv variables are passed, so strict policy wrappers that validate an allowlisted environment (or treat OPENCLAW_* as control input) now see an unexpected variable. Please use an exact-env/no-marker path for policy commands (and the same exec-provider pattern) instead of the generic child-process marker path.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 699cfe0fc5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/secrets/resolve.ts
Comment on lines +638 to +640
baseEnv: {},
cwd: path.dirname(secureCommandPath),
env: childEnv,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve exact exec-provider environments

When an exec SecretRef provider is configured with env/passEnv, this now runs through the generic command wrapper, whose resolveCommandEnv() always adds OPENCLAW_CLI=1 even with baseEnv: {}. That violates the documented minimal exec-provider environment contract (docs/gateway/configuration-reference.md says required variables must be passed explicitly with passEnv), so strict secret resolver wrappers or providers treating OPENCLAW_* as control input can start failing after this refactor. Use an exact-env/no-marker execution path for exec SecretRef providers, like the old spawn(..., { env: childEnv }) behavior.

Useful? React with 👍 / 👎.

@steipete
steipete force-pushed the codex/execa-process-sweep branch from d502d74 to f7948f9 Compare July 13, 2026 18:04
@steipete
steipete merged commit db02a96 into main Jul 13, 2026
103 checks passed
@steipete
steipete deleted the codex/execa-process-sweep branch July 13, 2026 18:07

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f7948f98ba

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

const resolvedTimeoutMs =
typeof timeoutMs === "number" ? resolveTimerTimeoutMs(timeoutMs, 1) : undefined;
const hasInput = input !== undefined;
const stdio = resolveCommandStdio({ hasInput, preferInherit: true });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep bounded commands off inherited stdin

When callers do not provide input, this now makes every bounded command inherit the gateway process stdin, even though many migrated sites previously used stdio: ["ignore", ...]. A concrete affected path is the SSH node-pairing probe in src/gateway/node-pairing-ssh-verify.runtime.ts:73: its own comments treat the target as untrusted until identity verification succeeds, and OpenSSH documents -n as redirecting stdin from /dev/null (https://man.openbsd.org/ssh#n), but this invocation supplies neither -n nor ignored stdin. As a result, a pairing target can read or hold the gateway's terminal/stdin until timeout; please make ignored stdin the default for no-input bounded commands or require an explicit opt-in to inherit it.

Useful? React with 👍 / 👎.

wm0018 pushed a commit to wm0018/openclaw that referenced this pull request Jul 14, 2026
)

* refactor(process): centralize bounded command execution

* refactor(process): migrate core one-shot commands

* refactor(plugins): migrate one-shot commands

* fix(process): await Windows tree termination

* chore(plugin-sdk): refresh process runtime surface

* refactor(process): migrate remaining bounded commands

* refactor(process): normalize command result handling

* refactor(process): split execution responsibilities

* chore(plugin-sdk): refresh API baseline

* chore(process): remove release-owned changelog entry

* fix(process): narrow binary command input checks

* fix(process): cap sandbox command output

* fix(qa-lab): preserve exact node probe env

* chore(ci): refresh dead export baseline

* fix(process): preserve force-kill command deadlines

* fix(process): avoid post-exit timeout reclassification

* test(process): update scp staging wrapper mock

* test(process): update remaining wrapper mocks

* refactor(qa-lab): preserve Execa tar execution
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 14, 2026
)

* refactor(process): centralize bounded command execution

* refactor(process): migrate core one-shot commands

* refactor(plugins): migrate one-shot commands

* fix(process): await Windows tree termination

* chore(plugin-sdk): refresh process runtime surface

* refactor(process): migrate remaining bounded commands

* refactor(process): normalize command result handling

* refactor(process): split execution responsibilities

* chore(plugin-sdk): refresh API baseline

* chore(process): remove release-owned changelog entry

* fix(process): narrow binary command input checks

* fix(process): cap sandbox command output

* fix(qa-lab): preserve exact node probe env

* chore(ci): refresh dead export baseline

* fix(process): preserve force-kill command deadlines

* fix(process): avoid post-exit timeout reclassification

* test(process): update scp staging wrapper mock

* test(process): update remaining wrapper mocks

* refactor(qa-lab): preserve Execa tar execution
tzy-17 added a commit to tzy-17/openclaw that referenced this pull request Jul 18, 2026
trimTruncatedUtf8Boundary in src/process/exec-output.ts skipped UTF-8
boundary trimming entirely when process.platform === "win32". This left
partial UTF-8 code points at the truncation boundary of captured command
output, causing Buffer.toString("utf8") downstream to emit U+FFFD
replacement characters in truncated doctor/cron/plugin command output on
Windows.

The win32 skip was introduced in openclaw#106495 (Execa refactor) without
documented rationale. Node.js stdout is UTF-8 on Windows by default, so
the same boundary-trimming logic applies. Remove the platform guard so
truncated output is always trimmed on a valid UTF-8 code point boundary.

Regression tests in src/process/exec.test.ts mock process.platform to
"win32" and verify that finalizeCapturedOutput trims leading
continuation bytes in tail mode and trailing partial code points in
head mode, with no U+FFFD in the result.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling channel: imessage Channel integration: imessage channel: matrix Channel integration: matrix channel: voice-call Channel integration: voice-call cli CLI command changes commands Command implementations docs Improvements or additions to documentation extensions: acpx extensions: codex extensions: memory-wiki extensions: ollama extensions: onepassword 1Password plugin extensions: qa-lab extensions: tts-local-cli gateway Gateway runtime maintainer Maintainer-authored PR merge-risk: 🚨 auth-provider 🚨 May break OAuth, tokens, provider routing, model choice, or credentials. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. P1 High-priority user-facing bug, regression, or broken workflow. plugin: file-transfer plugin: logbook Logbook automatic work journal plugin plugin: mxc rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. scripts Repository scripts size: XL status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant