fix: avoid nesting dedicated Linux workers (#124636)

* fix: avoid nesting dedicated Linux workers

* fix: preserve worker routing prerequisites

---------

Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Peter Steinberger
2026-08-16 08:25:04 -07:00
committed by GitHub
parent eeffa53b20
commit 55240929f5
14 changed files with 216 additions and 75 deletions
+1 -1
View File
@@ -64,7 +64,7 @@ Read [references/live-proof-routing.md](references/live-proof-routing.md) before
2. Independently reproduce the actual current-main user path. Map the entry point, canonical owner, callers, callees, sibling implementations, state lifecycle, existing regressions, shipped contracts, and relevant direct upstream source. Identify why the current design fails before proposing a repair.
3. Refactor the canonical owner in an isolated worktree. Repair all affected sibling paths in the same coherent change, simplify or remove the defective abstraction, and carry authoritative facts through the existing lifecycle. Prefer the cleanest appropriately sized root-cause solution over a minimal diff; reject a guard, special case, extra cache, fallback, compatibility shim, or narrowly passing test that leaves the architectural defect behind.
4. Preserve public configuration, plugin ownership, gateway protocol, migrations, provider contracts, persistent state, and external dependencies. When a correct root-cause repair would change a sensitive contract or requires a product decision, prepare it for operator review; do not disguise that risk as a small autonomous fix.
5. Add authentic regression coverage for the original reproduction, affected siblings, lifecycle cleanup, and unchanged legitimate behavior. Run appropriately scoped proof on the exact candidate head. Route Docker, real providers, packaging, full checks, typechecking, broad suites, and browser work through the existing remote workflow; inspect actual exit status, nonzero scenario counts, and artifacts.
5. Add authentic regression coverage for the original reproduction, affected siblings, lifecycle cleanup, and unchanged legitimate behavior. Run appropriately scoped proof on the exact candidate head. Follow `$openclaw-testing` host routing: use the current dedicated Linux worker for capable trusted-source Docker, packaging, full checks, typechecking, broad suites, provider, and browser work; use the existing remote workflow when another capability or boundary is required. Inspect actual exit status, nonzero scenario counts, and artifacts.
6. Run a fresh `$autoreview` on the complete final refactor. Require the reviewer to compare owner boundaries and sibling implementations, confirm this is the best clean root-cause solution, and reject quick-fix residue even when tests pass. Resolve actionable findings; rerun review after any production, test, or head change. Personally read the latest ClawSweeper review, satisfy each applicable rank-up move with real evidence, and update the existing PR body before landing.
7. Check existing open PRs, current author counts, and the actual repository automation before publishing. Read both the current labeler and response policy; verify the authenticated author association, repository permission, account type, automation branch prefix, and actual override label. Apply only exemptions proved by that current policy, including eligible owners, maintainers, collaborators, bots or apps, approved automation branches, and explicit overrides. Never infer capacity from a truncated list or assume that one privileged role represents every exemption. Reuse and repair an existing candidate PR for the same cause. When a real cap applies, hold reviewed worktrees and finish or land existing verified work first.
8. Create a focused PR with the repository's actual template, canonical cause, user impact, frozen head, completed proof, and risk. Use only the current repo-native `scripts/pr` review, artifact, prepare, and merge workflow for authorized main landing.
@@ -51,13 +51,15 @@ Read the app's scoped owner guide. Record whether macOS app signing, physical
iOS/Android devices, simulators, Android emulators, or hosted runners were
actually available and exercised. Source inspection is not runtime proof.
## Remote execution
## Execution host
Follow `.agents/skills/crabbox/SKILL.md` for heavy trusted-source suites,
packaging, Docker, browser, and live provider proof. Acquire a trusted lease
only when needed and execute **one command at a time per lease**. Sync and
verify the exact candidate SHA before running. A 780-second timeout bounds an
individual subagent scenario after its setup completes.
Follow `.agents/skills/openclaw-testing/SKILL.md` for the current-host gate.
Run capable trusted-source Linux suites, packaging, Docker, browser, and live
provider proof directly on a dedicated Linux worker. When another capability
or boundary requires Crabbox, acquire a trusted lease only when needed and
execute **one command at a time per lease**. Sync and verify the exact candidate
SHA before running. A 780-second timeout bounds an individual subagent scenario
after its setup completes.
If an orchestrator process or session disappears, inspect the original remote
job, recorded command, and authoritative exit status before retrying. A missing
+4 -1
View File
@@ -28,7 +28,10 @@ node scripts/run-vitest.mjs run --config test/vitest/vitest.ui-e2e.config.ts --c
pnpm test:ui:e2e
```
If dependencies are missing in a Codex worktree, install once with `pnpm install`; for broad GUI proof or dependency-heavy checks, use Testbox/Crabbox instead of running a wide local pnpm lane.
Do not install dependencies into a linked/sparse worktree. For broad GUI proof
or dependency-heavy checks, use a prepared normal checkout on the current
dedicated Linux worker when it has browser support; otherwise use
Testbox/Crabbox instead of running a wide lane on a workstation.
## Visual Proof Default
+26 -5
View File
@@ -18,28 +18,49 @@ Backends:
Always report provider, id, run URL, command, result. Never call Testbox “AWS
Crabbox.”
## Host Gate
Do not acquire a remote Linux lease before classifying the current execution
host. A dedicated Linux worker is isolated/disposable, headless, assigned to the
current task, and safe for trusted repository code. Run suitable trusted Linux
tests, builds, Docker/package work, and heavy checks directly there; nesting
Crabbox only creates another machine with the same role.
Use generic properties only: Linux, no interactive desktop/user session, and a
virtualized/containerized boundary. For equivalent bare-metal or hidden
virtualization, `AGENT_HOST_ROLE=worker` is the explicit signal;
`AGENT_HOST_ROLE=workstation` opts out. Never match a session vendor, agent
runtime, hostname brand, or provider-specific environment variable. Ambiguity
means workstation. Explicit remote requests still win.
Use Crabbox from a dedicated worker only when proof needs a distinct capability
or boundary: another OS/device, desktop/browser support the worker lacks,
clean-room handling of untrusted source, credentials absent from the worker,
release/CI parity, or a user-requested remote backend.
## Route First
Source trust before test size.
Source trust, then current host, then test size/capability.
- Trusted + one/few focused tests + ready deps: local.
- Trusted + heavy proof: Blacksmith Testbox.
- Trusted + dedicated Linux worker + suitable Linux proof: current worker.
- Trusted + workstation + heavy proof: Blacksmith Testbox.
- Untrusted contributor/fork: secretless fork CI or sanitized direct AWS.
- Never untrusted code on credential-hydrated Testbox.
- Never run untrusted repo wrapper/config locally.
- No speculative warmup. Acquire when first heavy command ready. Reuse id. Stop
before handoff. Local proof fanning out/expensive: stop local, go remote.
before handoff. Workstation proof fanning out/expensive: stop local, go remote.
- Remote backend unavailable (broker/DNS/network/lease): trusted-source proof
falls back to local — including heavy suites/gates — instead of blocking.
Note fallback + reason in the proof summary. Untrusted source never falls
back to local.
Need direct AWS semantics? Pass `--provider aws`. Need normal trusted OpenClaw
heavy proof? Pass `--provider blacksmith-testbox`.
remote proof from a workstation? Pass `--provider blacksmith-testbox`.
## Preflight
Run from repo root.
Run from repo root only after the host gate says remote proof is needed.
```sh
command -v crabbox
+2 -1
View File
@@ -150,7 +150,8 @@ work, copy the DB into a dev state dir first.
- Worker/dist/lazy import/package surface: targeted tests plus `pnpm build`.
- Live provider/model behavior: same provider/model with debug flags and a real
key if available.
- Docker/package/Linux/CI-parity: `$crabbox`.
- Docker/package/Linux/CI-parity: current dedicated Linux worker when capable;
otherwise `$crabbox`.
- CI failure: exact SHA, relevant job only, logs only after failure/completion.
## Output Habit
@@ -332,8 +332,9 @@ gh search issues --repo openclaw/openclaw --match title,body --limit 50 \
Tests that source `scripts/pr-lib/*` directly must provide the same command
surface instead of weakening the production wrapper for a minimal test image.
- Classify source trust before executing code-changing or landing proof; acquire
the safe backend lazily through `$crabbox` at the first heavy proof, never
pre-warm it at task start. Trusted maintainer code defaults to Blacksmith Testbox;
a safe remote backend lazily through `$crabbox` only when the current host
gate requires one; never pre-warm it at task start. Trusted maintainer code
runs suitable proof on a dedicated Linux worker and otherwise defaults to Blacksmith Testbox;
contributor/fork code stays untrusted unless a maintainer explicitly approves
credentialed execution after review; it uses secretless fork CI or
sanitized direct AWS Crabbox with `CRABBOX_ENV_ALLOW=CI`,
@@ -84,8 +84,8 @@ proof choice, `$autoreview` pre-publish review, `$crabbox` heavy/remote proof.
recorded checkpoint.
- Classify source trust before execution. Untrusted contributor code, tests,
or wrappers never run locally or on a credential-hydrated host; use the
sanitized remote-proof path. Route heavy trusted proof to the selected
remote box.
sanitized remote-proof path. Run capable heavy trusted proof on the current
dedicated Linux worker; otherwise use the selected remote box.
- Before replacing a failed or interrupted worker, preserve its claimed items,
patches, checkout ownership, and evidence; hand them to exactly one
replacement without duplicating or discarding unfinished work.
@@ -93,8 +93,9 @@ barrels, package-boundary tests, or extension suites.
- runtime capture should be quiet and config-tolerant.
- command output should include wall time, exit code, and peak RSS when
available.
4. For broad or package-heavy plugin proof, use Crabbox-backed Blacksmith
Testbox by default on maintainer machines:
4. For broad or package-heavy plugin proof, use the current dedicated Linux
worker when available. On a maintainer workstation, use Crabbox-backed
Blacksmith Testbox:
- `pnpm crabbox:run -- --provider blacksmith-testbox --timing-json -- OPENCLAW_TESTBOX=1 pnpm test:extensions:batch <ids>`
- add `--keep`/`--id <id-or-slug>` only when several commands must share one
warmed box; stop it with `pnpm crabbox:stop -- <id-or-slug>`.
+41 -25
View File
@@ -18,20 +18,31 @@ or validating a change without wasting hours.
Prove the touched surface first. Do not reflexively run the whole suite.
Route by source trust first, then proof size. Only trusted source may run
locally; never execute untrusted repository tooling locally, regardless of
proof size. Run one/few focused tests and cheap static checks locally when the
existing dependency install is ready. Use a
remote backend for larger suites, changed gates with typecheck/lint fan-out,
builds, Docker, packaging, E2E, live proof, and cross-platform work. Trusted
maintainer heavy proof defaults to Blacksmith Testbox. Untrusted contributor
or fork code must use secretless fork CI or sanitized direct AWS Crabbox;
never sync or run it on the credential-hydrated Blacksmith workflow.
Route by source trust first, execution host second, then proof size and required
capabilities. Only trusted source may run on the current host; never execute
untrusted repository tooling there, regardless of proof size. Run focused tests
and cheap static checks wherever dependencies are ready. If the current host is
a dedicated Linux worker, run suitable heavy Linux proof there too: larger
suites, changed gates, builds, Docker, packaging, E2E/live work, and
typecheck/lint fan-out. Do not nest a remote Linux box only to replace an
existing dedicated Linux worker. On a developer workstation, or when proof
requires another OS, device, browser/desktop, trust boundary, credentials, or
prepared release environment, use the matching remote backend. Trusted
maintainer remote proof defaults to Blacksmith Testbox. Untrusted contributor
or fork code must use secretless fork CI or sanitized direct AWS Crabbox; never
sync or run it on the credential-hydrated Blacksmith workflow.
Do not pre-warm for anticipated work. Acquire the backend lazily when the
first heavy command is ready to run, save its id, reuse it for later heavy
commands, and stop it before handoff. A single late heavy command can remain a
one-shot.
Detect host role from generic properties, never session-provider or agent-product
names. A dedicated worker is Linux, isolated/disposable, headless, and assigned
to the current task. `check:changed` auto-detects a headless noninteractive
virtualized/containerized Linux host. Set `AGENT_HOST_ROLE=worker` for an
equivalent bare-metal or otherwise undetectable host, or
`AGENT_HOST_ROLE=workstation` to opt out. Treat ambiguity as a workstation.
Do not pre-warm for anticipated work. When a remote backend is actually needed,
acquire it lazily when the first remote command is ready, save its id, reuse it
for later remote commands, and stop it before handoff. A single late remote
command can remain a one-shot.
For untrusted heavy proof, switch to a clean trusted `main` checkout and lazily
warm direct AWS with an installed trusted Crabbox binary. Do not execute the
@@ -95,13 +106,16 @@ sync the current checkout on every run, and stop it before handoff.
1. Inspect the diff and classify the touched surface:
- trusted source, one/few focused tests with ready local dependencies:
`node scripts/run-vitest.mjs <path-or-filter>`
- if focused proof fans out, becomes expensive, or lacks ready dependencies:
acquire the safe remote backend selected by source trust
- on a dedicated Linux worker, install missing dependencies in a normal
checkout and run suitable heavy Linux proof directly
- on a workstation, if focused proof fans out, becomes expensive, or lacks
ready dependencies: acquire the safe remote backend selected by source trust
- changed gates, builds, typechecks, lint fan-out, Docker, package, E2E, or
live work: run it remotely; these are never routine laptop work
live work: run on the dedicated worker when it has the required capability;
otherwise use the matching remote workflow
- `check:changed` classifies first; docs-only, no-change, and small metadata
plans stay local when dependencies are ready, while heavy or dependency-
missing plans delegate remotely
plans stay on the current host; heavy or dependency-missing plans stay on a
dedicated worker but delegate from a workstation
- direct AWS Crabbox proof: pass `--provider aws`; untrusted code also
requires the sanitized invocation above
- workflow-only: `git diff --check`, workflow syntax/lint (`actionlint` when available)
@@ -114,14 +128,16 @@ sync the current checkout on every run, and stop it before handoff.
## Guardrails
- Do not kill unrelated processes or tests. If something is running elsewhere, treat it as owned by the user or another agent.
- Keep trusted-source local proof bounded to one/few focused tests and cheap
static checks with ready dependencies. Untrusted repository tooling never
runs locally. Full suites and computationally intensive commands run remotely.
- Keep trusted-source workstation proof bounded to one/few focused tests and
cheap static checks with ready dependencies. A dedicated worker may run full
suites and computationally intensive commands directly. Untrusted repository
tooling never runs on either host class.
- Prefer GitHub Actions for release/Docker proof when the workflow already has the prepared image and secrets.
- Use standard Git commands when committing; stage only your files.
- If dependencies are missing on the selected remote box, run `pnpm install` there, retry
once, then report the first actionable error. Do not reconcile or reinstall a
local Codex worktree merely to run validation.
- If dependencies are missing on the selected execution host, run `pnpm install`
there when it is a normal checkout, retry once, then report the first
actionable error. Do not reconcile or reinstall a linked/sparse worktree
merely to run validation.
- In a Codex worktree or linked/sparse checkout, do not run direct local
`pnpm test*`, `pnpm check*`, or `pnpm crabbox:run`. Use
`node scripts/crabbox-wrapper.mjs` for remote proof and
@@ -129,7 +145,7 @@ sync the current checkout on every run, and stop it before handoff.
`node scripts/run-vitest.mjs` for bounded focused local proof when the
dependency install is ready. Use `git commit --no-verify` only after the
relevant proof is already clean.
- For remote proof, use the Crabbox wrapper first, but name the actual backend.
- When remote proof is required, use the Crabbox wrapper first, but name the actual backend.
Direct AWS Crabbox uses `provider=aws` and `cbx_...` ids. Delegated
Blacksmith Testbox through Crabbox uses `provider=blacksmith-testbox`,
`syncDelegated=true`, and `tbx_...` ids. Both satisfy "remote proof" when the
@@ -36,18 +36,22 @@ pnpm changed:lanes --json
In Codex worktrees under `.codex/worktrees`, `node_modules` must be a symlink to
the main OpenClaw checkout. Do not run `pnpm install` there. For broad or
package-heavy proof, use Blacksmith Testbox or GitHub Actions.
package-heavy proof, use a prepared normal checkout on the current dedicated
Linux worker, Blacksmith Testbox, or GitHub Actions according to the required
artifact and capability boundary.
## Runner Choice
Prefer this order:
1. **GitHub Package Acceptance** for installable-package product proof.
2. **`ci-build-artifacts-testbox.yml` Testbox** when Docker/package lanes need
2. **Current dedicated Linux worker** for trusted source/package/Docker proof
when it has the required dependencies and capabilities.
3. **`ci-build-artifacts-testbox.yml` Testbox** when Docker/package lanes need
seeded `dist`, `dist-runtime`, and package caches.
3. **`ci-check-testbox.yml` Testbox** for source checks, targeted Vitest,
4. **`ci-check-testbox.yml` Testbox** for source checks, targeted Vitest,
package-boundary checks, or focused Docker lanes.
4. **Local targeted commands only** for small format/static/unit probes.
5. **Workstation targeted commands only** for small format/static/unit probes.
Avoid long package Docker runs from a stale sparse worktree. If Testbox sync
reports hundreds of changed files or starts deleting package inputs, stop and
+6 -5
View File
@@ -163,12 +163,13 @@ Review invariants; full doctrine: `docs/gateway/audit.md`.
- Runtime: Node 22.22.3+, 24.15+, or 25.9+; Node 26 recommended (CI and release workflows still pin Node 24). Keep Node + Bun paths working.
- Package manager/runtime: repo defaults only. No swaps without approval.
- Install: `pnpm install` (keep Bun lock/patches aligned if touched). Agent dependency installation for tests/builds defaults to the selected remote box.
- Install: `pnpm install` (keep Bun lock/patches aligned if touched). Install test/build dependencies on the selected execution host.
- CLI: `pnpm openclaw ...` or `pnpm dev`; build: `pnpm build`.
- Never run the CLI as `node --import tsx src/index.ts`: tsx compiles all bundled plugins per process (~220s), the cost lands inside the agent task budget, and the run fails as a misleading `no progress ... timed out`. Use the dist-backed wrappers above. (Scoped-guide `node --import tsx scripts/*.mts` tools are fine — this rule is about the CLI entrypoint.)
- Checkout classes for the rules below: a **normal checkout** is a full clone with its own installed `node_modules` (includes harness/PR worktrees that have them); a **worktree** here means any Codex, linked, sparse, or `node_modules`-less checkout where pnpm may prompt or reconcile dependencies.
- Execution-host routing: a dedicated Linux worker is an isolated, disposable, headless machine assigned to the current task, not a developer workstation. Run trusted Linux-capable proof there directly, including heavy checks; do not create a second Linux worker merely for isolation or compute. `check:changed` recognizes a headless noninteractive virtualized/containerized Linux host. For equivalent hosts that cannot be detected, set generic `AGENT_HOST_ROLE=worker`; set `AGENT_HOST_ROLE=workstation` to opt out. Do not key detection to a session vendor or agent product. Ambiguous hosts are workstations. A worker designation does not substitute for a required different OS, device, clean-room trust boundary, browser/desktop capability, credential boundary, or release workflow.
- Test commands, trusted source, one/few focused files, deps ready — normal checkout: `pnpm test <path-or-filter> [vitest args...]` (also `pnpm test:changed`, `test:serial`, `test:coverage`); worktree: `node scripts/run-vitest.mjs <path-or-filter>` only — never direct local `pnpm test*`, `pnpm check*`, or `pnpm crabbox:run` there, and never reconcile dependencies merely to keep proof local. Never raw `vitest`; if unavoidable, `vitest run ...` (bare `vitest` starts watch mode and never exits). No `--repeat`; use a bounded shell loop.
- Checks/lint, trusted source — normal checkout: `pnpm check:changed` classifies first; docs-only, no-change, and small metadata plans stay local when deps are ready, typecheck/lint fan-out delegates to Crabbox/Testbox; lanes: `pnpm changed:lanes --json`; staged/path forms `--staged` / `-- <files...>`. Worktree: `node scripts/check-changed.mjs [--staged|-- <files...>]` (classifies without installed deps, delegates heavy or dependency-missing proof). Untrusted source: never run these repository-controlled classifiers locally.
- Checks/lint, trusted source — normal checkout: `pnpm check:changed` classifies first; dedicated Linux workers run suitable plans directly, while workstations keep docs-only, no-change, and small metadata plans local when deps are ready and delegate typecheck/lint fan-out. Lanes: `pnpm changed:lanes --json`; staged/path forms `--staged` / `-- <files...>`. Worktree: `node scripts/check-changed.mjs [--staged|-- <files...>]` (classifies without installed deps and follows the same execution-host routing). Untrusted source: never run these repository-controlled classifiers locally.
- Extension tests: `pnpm test:extensions`, `pnpm test extensions`, `pnpm test extensions/<id>`.
- Typecheck: `tsgo` lanes only (`pnpm tsgo*`, `pnpm check:test-types`); never add `tsc --noEmit`, `typecheck`, `check:types`.
- Formatting: `oxfmt`, not Prettier. Normal checkout: `pnpm format <paths>` (no `format:write` script); worktree: `node_modules/.bin/oxfmt` directly. Checks use repo wrappers (`pnpm format:*`, `scripts/run-oxlint.mjs`; full `pnpm lint:*` only when scope requires).
@@ -176,14 +177,14 @@ Review invariants; full doctrine: `docs/gateway/audit.md`.
- Script implementations use TypeScript where their runtime supports `tsx`; plain-Node lifecycle, packaged, Docker, and loader closures remain JavaScript and are included in the scripts program through `allowJs`.
- Script wrappers: failing or crashed run must end with one final `[tool] FAILED (exit N)` stderr line; crash = nonzero exit. Truncated output must never read as success. Pattern: `scripts/run-oxlint.mjs`.
- Tooling crash `Cannot find module ...` right after pulling/merging main = stale `node_modules`, not a code bug. `pnpm install` first; only then debug.
- Build before push when build output, packaging, lazy/module boundaries, dynamic imports, or published surfaces can change; agent builds default to the selected remote box unless platform-specific proof requires another remote host.
- Build before push when build output, packaging, lazy/module boundaries, dynamic imports, or published surfaces can change; run on the selected execution host unless platform-specific proof requires another host.
## Validation
- Use `$openclaw-testing` for test/CI choice and `$crabbox` for remote/full/E2E proof.
- Proof routing: source trust first, proof size second. Trusted source runs focused tests, `git diff --check`, targeted formatting, and cheap static probes locally when deps are ready. Heavy proof — full suites, builds, Docker/packaging, E2E/live/cross-OS, typecheck/lint fan-out, anything computationally intensive — goes to Crabbox/Testbox (trusted maintainer default: Blacksmith Testbox); acquire the lease lazily at the first heavy proof, reuse it, stop it before handoff. Remote backend unavailable: trusted-source proof falls back to local (from a normal checkout when the worktree cannot run pnpm), including heavy gates; note fallback + reason. Lease/procedure mechanics: `$crabbox`.
- Proof routing: source trust first, execution host second, proof size/capabilities third. Trusted source runs focused tests and cheap static probes on any prepared host. A dedicated Linux worker also runs suitable full suites, builds, Docker/packaging, E2E/live work, and typecheck/lint fan-out directly when it has the required capability and credentials. On a workstation, or when another OS/device/capability/isolation boundary is required, heavy proof goes to Crabbox/Testbox (trusted maintainer default: Blacksmith Testbox); acquire the lease lazily, reuse it, and stop it before handoff. Never nest Crabbox only to replace one dedicated Linux worker with another. Remote backend unavailable: trusted-source proof falls back to the current suitable host (from a normal checkout when the worktree cannot run pnpm); note fallback + reason. Lease/procedure mechanics: `$crabbox`.
- Untrusted (contributor/fork) source: never run its scripts, tests, checks, wrappers, config, or package hooks locally, regardless of proof size, and never fall back to local. Use secretless fork CI or the sanitized direct AWS Crabbox procedure in `$crabbox`, never a credential-hydrated Testbox. Maintainer approval of credentialed execution after review makes it trusted; an explicit owner/maintainer instruction to land named, reviewed PRs is that approval — do not ask twice.
- Visual proof: use Crabbox, set up like a user, then screenshot-verify. No harness/bypass/shortcut unless explicitly asked.
- Visual proof: use a real isolated browser/desktop on the current dedicated worker when capable; otherwise use Crabbox. Set up like a user, then screenshot-verify. No harness/bypass/shortcut unless explicitly asked.
- UI-visible change (Control UI, native app, or user-visible chat/session behavior): before/after screenshots or a short video are mandatory PR evidence, captured from a real running surface and sanitized. Exception: channel-visible chat behavior may satisfy the real-behavior-proof gate via the mock-gateway harness verdict (ClawSweeper section) when it covers the changed path; live proof is stronger. UI proof infeasible: state the exact blocker in the PR.
- Gateway-behavior change provable in the Control UI (session lifecycle, steering/queue, subagent flows, delivery states): prove on a live dev gateway — isolated `OPENCLAW_STATE_DIR`, own port, never the operator's gateway — and attach a video of the flow. Default recorder: Playwright `recordVideo` against the dashboard URL; keep the driving script's waits on asserted UI states, not sleeps.
- Repo-native PR worktrees may omit `node_modules`; prove remotely, then use `git commit --no-verify`.
+15 -15
View File
@@ -27,21 +27,21 @@ new directory taxonomy.
## Common Entry Points
| Area | Prefer | Notes |
| --------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ |
| Build | `pnpm build` | Runs `scripts/build-all.mts`; use specific build scripts only when debugging a build stage. |
| Changed checks | `pnpm changed:lanes --json`, `pnpm check:changed` | Lane classification lives in `scripts/changed-lanes.mjs`; changed-file checks live in `scripts/check-changed.mjs`. |
| Docs | `pnpm docs:list`, `pnpm docs:check-mdx`, `pnpm docs:check-links` | Backed by `scripts/docs-list.js`, `scripts/check-docs-mdx.mjs`, and `scripts/docs-link-audit.mjs`. |
| Formatting docs | `pnpm format:docs:check` | Uses `scripts/format-docs.mts`; use write mode only when intentionally formatting docs. |
| Lint | `pnpm lint`, `pnpm lint:core`, `pnpm lint:all` | Wrapper scripts keep oxlint behavior aligned with repo config. |
| Targeted tests | `pnpm test <path-or-filter>` or `node scripts/run-vitest.mjs <path-or-filter>` | Avoid bare `vitest`; it can start watch mode. |
| Changed tests | `pnpm test:changed` | Uses the repo's changed-test resolver instead of a broad Vitest run. |
| Docker proof | `pnpm test:docker:all`, `pnpm test:docker:rerun`, `pnpm test:docker:timings` | Use the planner/rerun helpers before launching broad Docker work. |
| Live proof | `pnpm test:live` | Live checks require the matching environment and credentials. |
| Release checks | `pnpm release:check`, `pnpm release:beta`, `pnpm release:candidate` | Release scripts are maintainer workflows; read release docs before use. |
| GitHub reads | `scripts/gh-read` | Uses a GitHub App read token when configured, leaving normal `gh` login for writes. |
| Commits | `git add <files...>` then `git commit -m "<message>"` | Stage only the intended files for each commit. |
| Remote proof | `node scripts/crabbox-wrapper.mjs ...` | Agent default for tests and heavy work; pre-warm by source trust, sync each run, reuse the lease. |
| Area | Prefer | Notes |
| --------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------- |
| Build | `pnpm build` | Runs `scripts/build-all.mts`; use specific build scripts only when debugging a build stage. |
| Changed checks | `pnpm changed:lanes --json`, `pnpm check:changed` | Lane classification lives in `scripts/changed-lanes.mjs`; changed-file checks live in `scripts/check-changed.mjs`. |
| Docs | `pnpm docs:list`, `pnpm docs:check-mdx`, `pnpm docs:check-links` | Backed by `scripts/docs-list.js`, `scripts/check-docs-mdx.mjs`, and `scripts/docs-link-audit.mjs`. |
| Formatting docs | `pnpm format:docs:check` | Uses `scripts/format-docs.mts`; use write mode only when intentionally formatting docs. |
| Lint | `pnpm lint`, `pnpm lint:core`, `pnpm lint:all` | Wrapper scripts keep oxlint behavior aligned with repo config. |
| Targeted tests | `pnpm test <path-or-filter>` or `node scripts/run-vitest.mjs <path-or-filter>` | Avoid bare `vitest`; it can start watch mode. |
| Changed tests | `pnpm test:changed` | Uses the repo's changed-test resolver instead of a broad Vitest run. |
| Docker proof | `pnpm test:docker:all`, `pnpm test:docker:rerun`, `pnpm test:docker:timings` | Use the planner/rerun helpers before launching broad Docker work. |
| Live proof | `pnpm test:live` | Live checks require the matching environment and credentials. |
| Release checks | `pnpm release:check`, `pnpm release:beta`, `pnpm release:candidate` | Release scripts are maintainer workflows; read release docs before use. |
| GitHub reads | `scripts/gh-read` | Uses a GitHub App read token when configured, leaving normal `gh` login for writes. |
| Commits | `git add <files...>` then `git commit -m "<message>"` | Stage only the intended files for each commit. |
| Remote proof | `node scripts/crabbox-wrapper.mjs ...` | Use when the current host lacks the required isolation, OS, device, or capacity; sync each run and reuse the lease. |
## Script Families
+37
View File
@@ -62,6 +62,9 @@ type ChangedCheckDelegateOptions = {
cwd?: string;
result?: ChangedLaneResult;
diffRefsReady?: boolean;
interactive?: boolean;
platform?: NodeJS.Platform;
virtualized?: boolean;
};
type ChangedCheckRunOptions = ChangedCheckPlanOptions & {
@@ -222,6 +225,37 @@ export function changedCheckRequiresRemote(result?: ChangedLaneResult) {
);
}
function isDedicatedLinuxWorker(
env: NodeJS.ProcessEnv,
options: Pick<ChangedCheckDelegateOptions, "interactive" | "platform" | "virtualized">,
) {
if ((options.platform ?? process.platform) !== "linux") {
return false;
}
const role = env.AGENT_HOST_ROLE?.trim().toLowerCase();
if (role === "worker") {
return true;
}
if (role === "workstation") {
return false;
}
if (env.DISPLAY || env.WAYLAND_DISPLAY || env.SSH_TTY) {
return false;
}
if ((options.interactive ?? process.stdin.isTTY) === true) {
return false;
}
if (options.virtualized !== undefined) {
return options.virtualized;
}
try {
execFileSync("systemd-detect-virt", ["--quiet"], { stdio: "ignore" });
return true;
} catch {
return false;
}
}
export function shouldDelegateChangedCheckToCrabbox(
argv: string[] = [],
env: NodeJS.ProcessEnv = process.env,
@@ -246,6 +280,9 @@ export function shouldDelegateChangedCheckToCrabbox(
if (isOpenEndedTruthyValue(env.OPENCLAW_TESTBOX)) {
return true;
}
if (isDedicatedLinuxWorker(env, options)) {
return !changedCheckLocalDependenciesReady(options.cwd ?? process.cwd());
}
// Release metadata plans diff the supplied commits after classification. A missing
// ref needs the hydrated remote checkout even when the explicit path itself is cheap.
if (result.lanes.releaseMetadata && options.diffRefsReady === false) {
+60 -6
View File
@@ -326,6 +326,7 @@ describe("scripts/changed-lanes", () => {
...createNestedGitEnv(),
CI: "",
GITHUB_ACTIONS: "",
AGENT_HOST_ROLE: "workstation",
OPENCLAW_CHECK_CHANGED_REMOTE_CHILD: "",
OPENCLAW_TESTBOX: "",
PATH: `${binDir}:${process.env.PATH ?? ""}`,
@@ -1134,7 +1135,7 @@ describe("scripts/changed-lanes", () => {
shouldDelegateChangedCheckToCrabbox(
["--base", "origin/main"],
{ PATH: "/usr/bin" },
{ result },
{ platform: "darwin", result },
),
).toBe(true);
expect(changedCheckRequiresRemote(result)).toBe(true);
@@ -1171,7 +1172,7 @@ describe("scripts/changed-lanes", () => {
const result = detectChangedLanes(["src/config/config.ts"]);
expect(changedCheckRequiresRemote(result)).toBe(true);
expect(shouldDelegateChangedCheckToCrabbox([], {}, { result })).toBe(true);
expect(shouldDelegateChangedCheckToCrabbox([], {}, { platform: "darwin", result })).toBe(true);
});
it("adds the dead export scan only for production source changes", () => {
@@ -1204,9 +1205,13 @@ describe("scripts/changed-lanes", () => {
expect(shouldDelegateChangedCheckToCrabbox([], {}, { cwd: dir, result: noChangesResult })).toBe(
false,
);
expect(shouldDelegateChangedCheckToCrabbox([], {}, { cwd: dir, result: docsResult })).toBe(
true,
);
expect(
shouldDelegateChangedCheckToCrabbox(
[],
{},
{ cwd: dir, platform: "darwin", result: docsResult },
),
).toBe(true);
writeRepoFile(dir, "node_modules/.modules.yaml", "layoutVersion: 5\n");
writeRepoFile(dir, "node_modules/.bin/oxfmt", "#!/bin/sh\n");
@@ -1231,7 +1236,56 @@ describe("scripts/changed-lanes", () => {
]);
expect(result.docsOnly).toBe(true);
expect(changedCheckRequiresRemote(result)).toBe(true);
expect(shouldDelegateChangedCheckToCrabbox([], {}, { cwd: repoRoot, result })).toBe(true);
expect(
shouldDelegateChangedCheckToCrabbox([], {}, { cwd: repoRoot, platform: "darwin", result }),
).toBe(true);
});
it("runs trusted changed gates on a dedicated Linux worker", () => {
const dir = makeTempRepoRoot(tempDirs, "openclaw-check-changed-worker-route-");
const result = detectChangedLanes(["src/config/config.ts"]);
const detectedWorker = {
cwd: dir,
interactive: false,
platform: "linux" as const,
result,
virtualized: true,
};
expect(
shouldDelegateChangedCheckToCrabbox(
[],
{},
{
interactive: false,
platform: "linux",
virtualized: true,
},
),
).toBe(true);
expect(shouldDelegateChangedCheckToCrabbox([], {}, detectedWorker)).toBe(true);
writeRepoFile(dir, "node_modules/.modules.yaml", "layoutVersion: 5\n");
writeRepoFile(dir, "node_modules/.bin/oxfmt", "#!/bin/sh\n");
writeRepoFile(dir, "node_modules/typescript/package.json", '{"name":"typescript"}\n');
expect(shouldDelegateChangedCheckToCrabbox([], {}, detectedWorker)).toBe(false);
expect(
shouldDelegateChangedCheckToCrabbox([], {}, { ...detectedWorker, interactive: true }),
).toBe(true);
expect(
shouldDelegateChangedCheckToCrabbox(
[],
{ AGENT_HOST_ROLE: "worker" },
{ platform: "linux", result, virtualized: false },
),
).toBe(false);
expect(
shouldDelegateChangedCheckToCrabbox([], { AGENT_HOST_ROLE: "workstation" }, detectedWorker),
).toBe(true);
expect(shouldDelegateChangedCheckToCrabbox([], { OPENCLAW_TESTBOX: "1" }, detectedWorker)).toBe(
true,
);
});
it("delegates staged changed gates as explicit remote paths", () => {