[PR #301] [MERGED] feat(groom): hoist agent-sandbox pre-exec validation out of the billed agent step #301

Closed
opened 2026-10-26 22:38:14 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/Comfy-Org/github-workflows/pull/301
Author: @mattmillerai
Created: 9/17/2026
Status: ✅ Merged
Merged: 9/18/2026
Merged by: @mattmillerai

Base: main ← Head: matt/be-14771-groom-sandbox-validate-only


📝 Commits (10+)

  • ad1036d feat(groom): run the finder/verifier/builder agents inside the sandbox + key broker
  • eff84d9 fix(groom): address cursor-review panel findings on the sandbox+broker wiring
  • 4e80b8e Merge origin/main into matt/be-4303-groom-sandbox-wire
  • c4a8fd1 feat(groom): split sandbox preflight into its own step before Run finder/verifier/builder
  • 3005fab fix(groom): make --preflight-only fail loud on misuse and drop the misleading --selftest alias
  • 2b2539f Merge remote-tracking branch 'origin/main' into matt/be-14756-groom-sandbox-preflight-split
  • bebca74 feat(groom): hoist agent-sandbox pre-exec validation out of the billed agent step
  • 025b801 fix(groom): close the --validate-only gaps the review panel found (BE-14771)
  • 7fd4200 Merge remote-tracking branch 'origin/main' into matt/be-14771-groom-sandbox-validate-only
  • 3132f6e Merge remote-tracking branch 'origin/main' into matt/be-14771-groom-sandbox-validate-only

📊 Changes

5 files changed (+621 additions, -66 deletions)

View changed files

📝 .github/groom/README.md (+74 -3)
📝 .github/groom/agent-sandbox.sh (+117 -7)
📝 .github/groom/tests/sandbox-tests.sh (+201 -0)
📝 .github/groom/tests/test_interval.py (+129 -15)
📝 .github/workflows/groom.yml (+100 -41)

📄 Description

#297 merged into main; GitHub retargeted this PR's base to main automatically, so it is no longer stacked. Its diff also picked up a semantic conflict with #300 (sandbox-tests.sh §7 renumbering + a shared final-echo line), resolved in the merge commit below — both PRs' test sections and README prose are preserved.

ELI-5

The sandbox wrapper checks a pile of things before it starts the agent — are the paths absolute, does the clone exist, is the key broker actually alive, does the output directory overlap the repo. All of those checks are free: if one fails, the agent never starts and nothing is billed. The problem is where they run. They run inside the step named "Run finder", so when one fails, the cadence gate sees a failed "Run finder" step, concludes the audit was spent, and pushes the next scheduled groom a whole interval into the future — for a run that spent nothing. This moves those checks into the step that already does the free setup work, where a failure costs nothing and counts for nothing.

What changed

agent-sandbox.sh — new --validate-only. It parses exactly like a real run (--clone / --clone-mode / --out-dir / --uds / --ro-file / --env) and walks the identical code path, branching at the single exec bwrap line instead of re-implementing the checks. That routing is the point: several guards live inside the bwrap_args assembly loop (--env KEY=VALUE, the rw-git-ro .git-pointer check, --ro-file absolute paths), and a separate validator would pass while the real step still died no-spend on one of them. It refuses a -- <command> and refuses to be combined with --preflight-only, mirroring that flag's misuse-guard style — so a stray --validate-only on a real agent step dies loudly rather than exiting 0 having run no agent.

groom.yml — all three agent jobs. The existing Preflight the sandbox step now runs --preflight-only then --validate-only with the same arguments the corresponding Run <agent> step uses (rw-git-ro for the builder). The step name is unchanged, so interval.py's exact-name match still never counts it. set -e is spelled out there now: the step runs two commands, and a bring-up failure must not be masked by the second one's exit status.

Tests. sandbox-tests.sh §9 stubs bwrap so "did it exec the jail?" is asserted rather than assumed — the stub logs every invocation, and the real exec is identifiable by --clearenv (the preflight self-test probe never passes it). §9b is the control: the same arguments without --validate-only do reach the exec, so the no-jail assertions cannot pass for the wrong reason. test_interval.py gains a comment plus two structural pins (see judgment calls).

Verification of the artifacts this touches

  • --validate-only on good args, against the live broker socket from §5 — exits 0, prints the success line, no bwrap exec.
  • Bad args, each hitting a different guard — missing --clone, relative --out-dir, unknown --clone-mode, out-dir nested in the clone, --env without =, relative --ro-file, and rw-git-ro over a gitdir: pointer file. All non-zero, no exec.
  • The failure this was written for — a socket bound and listened on by a process that then exited without closing it: -S passes, /healthz fails. Non-zero, no exec.
  • Misuse — --validate-only --preflight-only (both orders) and --validate-only … -- true both die.
  • Mutation-tested the new assertions rather than trusting them. A --validate-only that branches early (before the required-arg checks) is caught by §9c; one that branches after preflight() but before the mount assembly is caught by the --env case — the exact regression the "route through the real path" requirement exists to prevent. On the workflow side, deleting the --validate-only invocation and dropping a single --ro-file from it each fail test_interval.py. A first draft of that assertion matched the flag name and kept passing against the step's own prose after the command was deleted; it now matches the invocation.
  • Cross-checked the three argument lists token for token against their Run <agent> steps — all three match.

Review round (cursor-review panel, 4 findings — all fixed in 025b801)

Every one was a guard --validate-only claimed to cover but did not, so every
one left its failure on the billed step — the exact miscount the hoist exists to
prevent.

  1. 🟡 --ro-file sources were checked for absoluteness but not existence.
    --ro-bind (not --ro-bind-try) aborts on a missing source, so an absent
    brief or jail-shim passed validation and then killed Run <agent> no-spend.
    [[ -e "$f" ]] now sits beside the absolute-path check in the same
    bwrap_args loop. Confirmed it cannot false-fail the real caller: every
    --ro-file groom.yml passes is produced by an unconditional step that
    precedes Preflight the sandbox in all three agent jobs.
  2. 🟢 The broker liveness probe was command -v curl-gated, so a curl-less
    host exited 0 on a stale socket — the crashed-broker case named as this
    split's most plausible live trigger. Extracted as broker_healthz(): curl
    preferred, python3 fallback, and only a host with neither is fatal —
    fatal under --validate-only alone. A real run keeps the best-effort skip.
  3. 🟢 The preflight/billed mirror assertion was scoped to audit_find,
    leaving the verifier and builder — the two with the longer --ro-file lists
    and the only rw-git-ro — unpinned. New SandboxPreflightHoistTest
    parameterizes ordering, both invocations, and the token-for-token argument
    mirror over all three jobs; the audit_find-only copy is replaced by a
    pointer so there is no second copy to drift.
  4. 🟢 The README overclaimed --validate-only as side-effect-free. It
    branches after preflight(), so standalone it runs the bring-up's
    apt-get/AppArmor/sysctl mutations. The claim is now scoped to "once the
    bring-up has already succeeded", with the mutations named. groom.yml's
    residual comment is corrected the same way — a missing --ro-file is no
    longer listed among the failures validation cannot reach.

Each fix is mutation-tested: deleting the -e check, the no-probe-tool fatal
branch, the python3 fallback, a builder --ro-file, or the verifier's
--validate-only call each fail their own assertion and nothing else.

Negative-claim falsification

The diff adds two deny paths (--preflight-only and --validate-only are mutually exclusive; --validate-only takes no -- <command>). Neither denies a capability that previously existed — --validate-only is introduced by this diff, and the pre-existing a -- <command...> is required check for real runs is preserved byte-for-byte in the else branch, not broadened. Empirically: §9b runs the full real path (same arguments, no --validate-only, a -- true command) through the stubbed bwrap and it still reaches the exec, and the pre-existing suite's sections 1–7 still drive real sandboxed commands. So nothing that worked before is refused now.

The review round adds two more deny paths, both falsified empirically:

  • A missing --ro-file now dies host-side. This denies nothing: --ro-bind
    already aborted on a missing source, so the run failed either way — only the
    step name and the error message change. Sections 1–7 still drive real
    --ro-file invocations through the jail, and §9b's control still reaches the
    exec.
  • --validate-only with no probe tool now dies. Rather than ship the
    dead-end the finding proposed (fatal on missing curl), the capability is
    preserved by a python3 fallback: §9f proves --validate-only still succeeds
    against a LIVE broker on a PATH containing python3 but no curl, and still
    catches §9d's stale socket there. The die fires only where neither tool
    exists, and §9f's last control proves a real run on that same PATH still
    reaches the exec — so the refusal is scoped to the one case where the mode
    genuinely cannot do its job.

Judgment calls

  1. --validate-only rejects a -- <command> rather than tolerating one. The plan allowed either relaxing the "command required" check or having callers pass -- true. Rejecting is the stricter reading: it keeps the relaxation from becoming a silent green no-op if the flag is ever pasted onto a real agent step. Cost: you cannot copy a real invocation verbatim and append the flag — you must drop the command, and the error message says so.
  2. The validate call omits the --envs. Every --env in groom.yml has a literal key, so the KEY=VALUE guard cannot fire from this caller, and duplicating the multi-line PROMPT into a second place is a drift hazard for no coverage. The other guards in that same loop (--ro-file, --clone-mode) are exercised. Stated in the step comment.
  3. test_interval.py got two assertions, not just the comment the plan asked for. One pins that the preflight step actually invokes both flags and that the billed step does not; one pins that the --validate-only argument list mirrors the agent step's. The step comment says "keep the two lists in sync", and a comment is not a guard. Both are scoped to audit_find, matching that file's existing scope.
  4. .github/groom/README.md documents both halves of the split (--preflight-only was merged in #297 without a README entry), since documenting the second half alone would read as if the first did not exist.

Residual

  • The window between Preflight the sandbox and Run <agent> is still open, by design. A broker that dies after the healthz probe, or a --ro-file that is deleted in between, still kills the billed step with no spend — and interval.py still counts that as a spent audit. This is the same residual the bring-up split carries. Proving the agent actually BILLED (the alternative fix, at the interval.py end) is the tracked follow-up; it is a distinct approach, not a duplicate of this one.
  • A second, related class this cannot reach: failures raised by bwrap itself at exec — a non-existent --ro-file, a mount that fails. Validating without running the jail is the whole point, so these are structurally out of reach of --validate-only and land on the billed step, no-spend, counted. Stated in the step comment so the window is not implied to be closed.
  • Design tension a reviewer should weigh: a separate proposal moves Preflight the sandbox ahead of Start the key broker. If that lands, the healthz half of --validate-only cannot run in a pre-broker step — the two would need splitting into a broker-independent arg/path pass and a broker-dependent healthz pass. This PR deliberately does not pre-empt that; it keeps the whole validation in a step that runs after the broker is up.
  • Unexercised artifacts. The change is only reachable in a real groom run, which bills an agent, so no end-to-end groom run was performed — the jail is exercised only through sandbox-tests.sh. On this host, sections 1–4 and 7–9 of that suite pass but section 5's in-jail assertion cannot: node lives outside /usr here and so is invisible inside the jail. Section 9 was therefore run through a local harness that sets up the same fixtures and a live broker; CI's sandbox-tests job runs the whole file on ubuntu-latest, where node is under /usr/local, and is the authority.

Provenance

  • Authored by: agent-work loop
  • Verified: shellcheck -x .github/groom/agent-sandbox.sh .github/groom/tests/sandbox-tests.sh: clean; python3 -m unittest discover -s .github/groom/tests -p 'test_*.py': 405 tests, 0 failures (402 + #300's 3 new test_scope.py cases picked up by the merge); sandbox-tests.sh §§8–9 via a local fixture harness with a live broker: 10 assertions, all PASS (§5 not runnable on this host — see Residual; §§1–4 pass unchanged); mutation-tested all five new guards/assertions individually; check_workflow_pins.py: OK, 11 workflows, 0 defaults; check_agents_md.py --root .: passed (2 pre-existing warnings). After merging origin/main to pick up #298–#300: resolved the sandbox-tests.sh/README conflicts by keeping this PR's §9 content and #300's §7e renumbering plus its conditional final ALL SANDBOX TESTS PASSED (N skipped) echo side by side; CI (sandbox-tests, unittest, unittest + lint this repo, AGENTS.md integrity, CodeRabbit) all green on the merged tree; diff vs. origin/main still 5 files, +621/-66, matching the pre-merge delta.
  • Deviations: one, against the review panel rather than the plan — finding 2 proposed making a missing curl fatal under --validate-only; shipped a python3 fallback instead, so the probe stays structural without denying the mode on a curl-less host. The four judgment calls above are unchanged and still additive.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/Comfy-Org/github-workflows/pull/301 **Author:** [@mattmillerai](https://github.com/mattmillerai) **Created:** 9/17/2026 **Status:** ✅ Merged **Merged:** 9/18/2026 **Merged by:** [@mattmillerai](https://github.com/mattmillerai) **Base:** `main` ← **Head:** `matt/be-14771-groom-sandbox-validate-only` --- ### 📝 Commits (10+) - [`ad1036d`](https://github.com/Comfy-Org/github-workflows/commit/ad1036de6dc49263692337167dd2fe5a47842de5) feat(groom): run the finder/verifier/builder agents inside the sandbox + key broker - [`eff84d9`](https://github.com/Comfy-Org/github-workflows/commit/eff84d94cabda078d6cd5ba70dfaea2267d3dcc5) fix(groom): address cursor-review panel findings on the sandbox+broker wiring - [`4e80b8e`](https://github.com/Comfy-Org/github-workflows/commit/4e80b8ee0ebab388eaf608c9d197d9e21e1e9aab) Merge origin/main into matt/be-4303-groom-sandbox-wire - [`c4a8fd1`](https://github.com/Comfy-Org/github-workflows/commit/c4a8fd150ee73e1fc295f1ecd790c079515ffdde) feat(groom): split sandbox preflight into its own step before Run finder/verifier/builder - [`3005fab`](https://github.com/Comfy-Org/github-workflows/commit/3005fabedd1f10d523fa3dcfad331612677d0577) fix(groom): make --preflight-only fail loud on misuse and drop the misleading --selftest alias - [`2b2539f`](https://github.com/Comfy-Org/github-workflows/commit/2b2539f40d01c464ddcf258d18104c2bda831ea6) Merge remote-tracking branch 'origin/main' into matt/be-14756-groom-sandbox-preflight-split - [`bebca74`](https://github.com/Comfy-Org/github-workflows/commit/bebca743a72364eb214136ba1d3f4af0023ca2e2) feat(groom): hoist agent-sandbox pre-exec validation out of the billed agent step - [`025b801`](https://github.com/Comfy-Org/github-workflows/commit/025b8014ea8f84a04d5d69f78ef82c3dc89101a9) fix(groom): close the --validate-only gaps the review panel found (BE-14771) - [`7fd4200`](https://github.com/Comfy-Org/github-workflows/commit/7fd420073731a7c4627745566c42f6060fe3e05d) Merge remote-tracking branch 'origin/main' into matt/be-14771-groom-sandbox-validate-only - [`3132f6e`](https://github.com/Comfy-Org/github-workflows/commit/3132f6e0265d3a9372d7ec346cb818263caf9c53) Merge remote-tracking branch 'origin/main' into matt/be-14771-groom-sandbox-validate-only ### 📊 Changes **5 files changed** (+621 additions, -66 deletions) <details> <summary>View changed files</summary> 📝 `.github/groom/README.md` (+74 -3) 📝 `.github/groom/agent-sandbox.sh` (+117 -7) 📝 `.github/groom/tests/sandbox-tests.sh` (+201 -0) 📝 `.github/groom/tests/test_interval.py` (+129 -15) 📝 `.github/workflows/groom.yml` (+100 -41) </details> ### 📄 Description _#297 merged into `main`; GitHub retargeted this PR's base to `main` automatically, so it is no longer stacked. Its diff also picked up a semantic conflict with #300 (`sandbox-tests.sh` §7 renumbering + a shared final-echo line), resolved in the merge commit below — both PRs' test sections and README prose are preserved._ ## ELI-5 The sandbox wrapper checks a pile of things before it starts the agent — are the paths absolute, does the clone exist, is the key broker actually alive, does the output directory overlap the repo. All of those checks are free: if one fails, the agent never starts and nothing is billed. The problem is *where* they run. They run inside the step named "Run finder", so when one fails, the cadence gate sees a failed "Run finder" step, concludes the audit was spent, and pushes the next scheduled groom a whole interval into the future — for a run that spent nothing. This moves those checks into the step that already does the free setup work, where a failure costs nothing and counts for nothing. ## What changed **`agent-sandbox.sh` — new `--validate-only`.** It parses exactly like a real run (`--clone` / `--clone-mode` / `--out-dir` / `--uds` / `--ro-file` / `--env`) and walks the *identical* code path, branching at the single `exec bwrap` line instead of re-implementing the checks. That routing is the point: several guards live inside the `bwrap_args` assembly loop (`--env KEY=VALUE`, the `rw-git-ro` `.git`-pointer check, `--ro-file` absolute paths), and a separate validator would pass while the real step still died no-spend on one of them. It refuses a `-- <command>` and refuses to be combined with `--preflight-only`, mirroring that flag's misuse-guard style — so a stray `--validate-only` on a real agent step dies loudly rather than exiting 0 having run no agent. **`groom.yml` — all three agent jobs.** The existing `Preflight the sandbox` step now runs `--preflight-only` *then* `--validate-only` with the same arguments the corresponding `Run <agent>` step uses (`rw-git-ro` for the builder). The step name is unchanged, so `interval.py`'s exact-name match still never counts it. `set -e` is spelled out there now: the step runs two commands, and a bring-up failure must not be masked by the second one's exit status. **Tests.** `sandbox-tests.sh` §9 stubs `bwrap` so "did it exec the jail?" is *asserted rather than assumed* — the stub logs every invocation, and the real exec is identifiable by `--clearenv` (the preflight self-test probe never passes it). §9b is the control: the same arguments *without* `--validate-only` do reach the exec, so the no-jail assertions cannot pass for the wrong reason. `test_interval.py` gains a comment plus two structural pins (see judgment calls). ## Verification of the artifacts this touches - **`--validate-only` on good args, against the live broker socket from §5** — exits 0, prints the success line, no `bwrap` exec. - **Bad args, each hitting a different guard** — missing `--clone`, relative `--out-dir`, unknown `--clone-mode`, out-dir nested in the clone, `--env` without `=`, relative `--ro-file`, and `rw-git-ro` over a `gitdir:` pointer file. All non-zero, no exec. - **The failure this was written for** — a socket bound and listened on by a process that then exited without closing it: `-S` passes, `/healthz` fails. Non-zero, no exec. - **Misuse** — `--validate-only --preflight-only` (both orders) and `--validate-only … -- true` both die. - **Mutation-tested the new assertions rather than trusting them.** A `--validate-only` that branches early (before the required-arg checks) is caught by §9c; one that branches after `preflight()` but before the mount assembly is caught by the `--env` case — the exact regression the "route through the real path" requirement exists to prevent. On the workflow side, deleting the `--validate-only` invocation and dropping a single `--ro-file` from it each fail `test_interval.py`. A first draft of that assertion matched the flag name and kept passing against the step's own prose after the command was deleted; it now matches the invocation. - **Cross-checked the three argument lists** token for token against their `Run <agent>` steps — all three match. ## Review round (cursor-review panel, 4 findings — all fixed in `025b801`) Every one was a guard `--validate-only` *claimed* to cover but did not, so every one left its failure on the billed step — the exact miscount the hoist exists to prevent. 1. **🟡 `--ro-file` sources were checked for absoluteness but not existence.** `--ro-bind` (not `--ro-bind-try`) aborts on a missing source, so an absent brief or jail-shim passed validation and then killed `Run <agent>` no-spend. `[[ -e "$f" ]]` now sits beside the absolute-path check in the same `bwrap_args` loop. Confirmed it cannot false-fail the real caller: every `--ro-file` groom.yml passes is produced by an **unconditional** step that precedes `Preflight the sandbox` in all three agent jobs. 2. **🟢 The broker liveness probe was `command -v curl`-gated**, so a curl-less host exited 0 on a stale socket — the crashed-broker case named as this split's most plausible live trigger. Extracted as `broker_healthz()`: curl preferred, **python3 fallback**, and only a host with *neither* is fatal — fatal under `--validate-only` alone. A real run keeps the best-effort skip. 3. **🟢 The preflight/billed mirror assertion was scoped to `audit_find`**, leaving the verifier and builder — the two with the longer `--ro-file` lists and the only `rw-git-ro` — unpinned. New `SandboxPreflightHoistTest` parameterizes ordering, both invocations, and the token-for-token argument mirror over all three jobs; the `audit_find`-only copy is replaced by a pointer so there is no second copy to drift. 4. **🟢 The README overclaimed `--validate-only` as side-effect-free.** It branches *after* `preflight()`, so standalone it runs the bring-up's `apt-get`/AppArmor/`sysctl` mutations. The claim is now scoped to "once the bring-up has already succeeded", with the mutations named. groom.yml's residual comment is corrected the same way — a missing `--ro-file` is no longer listed among the failures validation cannot reach. Each fix is mutation-tested: deleting the `-e` check, the no-probe-tool fatal branch, the python3 fallback, a builder `--ro-file`, or the verifier's `--validate-only` call each fail their own assertion and nothing else. ## Negative-claim falsification The diff adds two deny paths (`--preflight-only` and `--validate-only` are mutually exclusive; `--validate-only` takes no `-- <command>`). Neither denies a capability that previously existed — `--validate-only` is introduced by this diff, and the pre-existing `a -- <command...> is required` check for real runs is preserved byte-for-byte in the `else` branch, not broadened. Empirically: §9b runs the full real path (same arguments, no `--validate-only`, a `-- true` command) through the stubbed `bwrap` and it still reaches the exec, and the pre-existing suite's sections 1–7 still drive real sandboxed commands. So nothing that worked before is refused now. The review round adds two more deny paths, both falsified empirically: - **A missing `--ro-file` now dies host-side.** This denies nothing: `--ro-bind` already aborted on a missing source, so the run failed either way — only the step name and the error message change. Sections 1–7 still drive real `--ro-file` invocations through the jail, and §9b's control still reaches the exec. - **`--validate-only` with no probe tool now dies.** Rather than ship the dead-end the finding proposed (fatal on missing curl), the capability is preserved by a python3 fallback: §9f proves `--validate-only` still succeeds against a LIVE broker on a PATH containing python3 but no curl, and still catches §9d's stale socket there. The die fires only where neither tool exists, and §9f's last control proves a **real run** on that same PATH still reaches the exec — so the refusal is scoped to the one case where the mode genuinely cannot do its job. ## Judgment calls 1. **`--validate-only` rejects a `-- <command>` rather than tolerating one.** The plan allowed either relaxing the "command required" check or having callers pass `-- true`. Rejecting is the stricter reading: it keeps the relaxation from becoming a silent green no-op if the flag is ever pasted onto a real agent step. Cost: you cannot copy a real invocation verbatim and append the flag — you must drop the command, and the error message says so. 2. **The validate call omits the `--env`s.** Every `--env` in `groom.yml` has a literal key, so the `KEY=VALUE` guard cannot fire from this caller, and duplicating the multi-line `PROMPT` into a second place is a drift hazard for no coverage. The other guards in that same loop (`--ro-file`, `--clone-mode`) *are* exercised. Stated in the step comment. 3. **`test_interval.py` got two assertions, not just the comment the plan asked for.** One pins that the preflight step actually *invokes* both flags and that the billed step does not; one pins that the `--validate-only` argument list mirrors the agent step's. The step comment says "keep the two lists in sync", and a comment is not a guard. Both are scoped to `audit_find`, matching that file's existing scope. 4. **`.github/groom/README.md`** documents both halves of the split (`--preflight-only` was merged in #297 without a README entry), since documenting the second half alone would read as if the first did not exist. ## Residual - **The window between `Preflight the sandbox` and `Run <agent>` is still open, by design.** A broker that dies *after* the healthz probe, or a `--ro-file` that is deleted in between, still kills the billed step with no spend — and `interval.py` still counts that as a spent audit. This is the same residual the bring-up split carries. Proving the agent actually BILLED (the alternative fix, at the `interval.py` end) is the tracked follow-up; it is a distinct approach, not a duplicate of this one. - **A second, related class this cannot reach:** failures raised by `bwrap` *itself* at exec — a non-existent `--ro-file`, a mount that fails. Validating without running the jail is the whole point, so these are structurally out of reach of `--validate-only` and land on the billed step, no-spend, counted. Stated in the step comment so the window is not implied to be closed. - **Design tension a reviewer should weigh:** a separate proposal moves `Preflight the sandbox` ahead of `Start the key broker`. If that lands, the healthz half of `--validate-only` cannot run in a pre-broker step — the two would need splitting into a broker-independent arg/path pass and a broker-dependent healthz pass. This PR deliberately does not pre-empt that; it keeps the whole validation in a step that runs *after* the broker is up. - **Unexercised artifacts.** The change is only reachable in a real groom run, which bills an agent, so no end-to-end groom run was performed — the jail is exercised only through `sandbox-tests.sh`. On this host, sections 1–4 and 7–9 of that suite pass but section 5's in-jail assertion cannot: `node` lives outside `/usr` here and so is invisible inside the jail. Section 9 was therefore run through a local harness that sets up the same fixtures and a live broker; CI's `sandbox-tests` job runs the whole file on `ubuntu-latest`, where `node` is under `/usr/local`, and is the authority. ## Provenance - **Authored by:** agent-work loop - **Verified:** `shellcheck -x .github/groom/agent-sandbox.sh .github/groom/tests/sandbox-tests.sh`: clean; `python3 -m unittest discover -s .github/groom/tests -p 'test_*.py'`: 405 tests, 0 failures (402 + #300's 3 new `test_scope.py` cases picked up by the merge); `sandbox-tests.sh` §§8–9 via a local fixture harness with a live broker: 10 assertions, all PASS (§5 not runnable on this host — see Residual; §§1–4 pass unchanged); mutation-tested all five new guards/assertions individually; `check_workflow_pins.py`: OK, 11 workflows, 0 defaults; `check_agents_md.py --root .`: passed (2 pre-existing warnings). After merging `origin/main` to pick up #298–#300: resolved the `sandbox-tests.sh`/README conflicts by keeping this PR's §9 content and #300's §7e renumbering plus its conditional final `ALL SANDBOX TESTS PASSED (N skipped)` echo side by side; CI (`sandbox-tests`, `unittest`, `unittest + lint this repo`, `AGENTS.md integrity`, CodeRabbit) all green on the merged tree; diff vs. `origin/main` still 5 files, +621/-66, matching the pre-merge delta. - **Deviations:** one, against the review panel rather than the plan — finding 2 proposed making a missing `curl` fatal under `--validate-only`; shipped a python3 fallback instead, so the probe stays structural without denying the mode on a curl-less host. The four judgment calls above are unchanged and still additive. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-26 22:38:14 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Comfy-Org/github-workflows#301
No description provided.