A step that runs without a cell ends with everything it started, on a cancel, a timeout, or its own exit #64

Merged
grokbot merged 1 commit from grokbot/forge:fix/step-stop-whole-tree into main 2026-10-18 18:01:33 +00:00
Owner

Where the host cannot hold a cell (CI, a pod without bwrap), an install,
test or check step ran as a plain /bin/sh -c child in the forge's own
process group, and a cancel or timeout SIGKILLed the shell alone. What
the shell had started (bun install's scripts, a test runner and its
workers, a sleep) was re-parented and kept running in the checkout
after the step was reported cancelled; a superseded check kept burning
the machine beside its replacement.

The step now leads a process group of its own (detached, without a
cell only), a cancel or timeout kills the group, and once the shell has
exited, what is left of the group is killed. A cell is unchanged: its
tree dies with bubblewrap. signalGroup moves to module level, shared
with startProcess (#63).

Since a terminal's Ctrl-C no longer reaches a running check, the forge
stops checks without the roll's drain wait on a person's signal
(SIGINT, SIGHUP): they are cut off and queued again at once. A SIGTERM
(a release) still lets them finish within FORGE_CHECKS_DRAIN_S.

Validation

pipeline/validate on 82c7d253: baseline in 618s — bun 1.3.14, CI-shaped env (scrubbed vars, runner PATH), exact check.yml shard + forge.json commands.

step result time
bun install --frozen-lockfile ok (exit 0) 0s
forge.json check.fast BASELINE (exit 1) 130s
check.yml gate (1) pull_request BASELINE (exit 1) 15s
check.yml gate (2) pull_request ok (exit 0) 274s
check.yml gate (3) pull_request ok (exit 0) 199s
  • ⚠️ node v20.19.2 here, v22 on CI (tsc runs under node)
  • ⚠️ check.yml gate (1) pull_request stopped early at a BASELINE failure (fail-fast): its later files were not run in this step
test output (failures, totals)
▶ bun install --frozen-lockfile
  ok  exit 0  0s
▶ forge.json check.fast
(fail) egress, reported: an undeclared public host goes through and is counted; a private one never does > an undeclared public host is let through and counted [1.15ms] [shard 10]
Ran 2987 tests across 301 files in 113.8s; 1 failed.
classify: reran test/contain.test.ts test/subscriptions.test.ts test/web-phone.test.ts  alone on origin/main b27bb0c3 and on this branch
  BASELINE  exit 1  130s
▶ check.yml gate (1) pull_request
(fail) egress, reported: an undeclared public host goes through and is counted; a private one never does > an undeclared public host is let through and counted [1.38ms] [shard 3]
Ran 112 tests across 3 files in 14.4s; 1 failed.
classify: reran test/contain.test.ts  alone on origin/main b27bb0c3 and on this branch
  BASELINE  exit 1  15s
▶ check.yml gate (2) pull_request
Ran 1336 tests across 137 files in 273.9s (137 shards).
  ok  exit 0  274s
▶ check.yml gate (3) pull_request
Ran 1508 tests across 138 files in 198.5s (138 shards).
  ok  exit 0  199s
Where the host cannot hold a cell (CI, a pod without bwrap), an install, test or check step ran as a plain `/bin/sh -c` child in the forge's own process group, and a cancel or timeout SIGKILLed the shell alone. What the shell had started (`bun install`'s scripts, a test runner and its workers, a `sleep`) was re-parented and kept running in the checkout after the step was reported cancelled; a superseded check kept burning the machine beside its replacement. The step now leads a process group of its own (`detached`, without a cell only), a cancel or timeout kills the group, and once the shell has exited, what is left of the group is killed. A cell is unchanged: its tree dies with bubblewrap. `signalGroup` moves to module level, shared with `startProcess` (#63). Since a terminal's Ctrl-C no longer reaches a running check, the forge stops checks without the roll's drain wait on a person's signal (SIGINT, SIGHUP): they are cut off and queued again at once. A SIGTERM (a release) still lets them finish within FORGE_CHECKS_DRAIN_S. <!-- pipeline:validation --> ### Validation `pipeline/validate` on `82c7d253`: **baseline** in 618s — bun 1.3.14, CI-shaped env (scrubbed vars, runner PATH), exact check.yml shard + forge.json commands. | step | result | time | |---|---|---| | `bun install --frozen-lockfile` | ok (exit 0) | 0s | | `forge.json check.fast` | BASELINE (exit 1) | 130s | | `check.yml gate (1) pull_request` | BASELINE (exit 1) | 15s | | `check.yml gate (2) pull_request` | ok (exit 0) | 274s | | `check.yml gate (3) pull_request` | ok (exit 0) | 199s | - ⚠️ node v20.19.2 here, v22 on CI (tsc runs under node) - ⚠️ check.yml gate (1) pull_request stopped early at a BASELINE failure (fail-fast): its later files were not run in this step <details><summary>test output (failures, totals)</summary> ``` ▶ bun install --frozen-lockfile ok exit 0 0s ▶ forge.json check.fast (fail) egress, reported: an undeclared public host goes through and is counted; a private one never does > an undeclared public host is let through and counted [1.15ms] [shard 10] Ran 2987 tests across 301 files in 113.8s; 1 failed. classify: reran test/contain.test.ts test/subscriptions.test.ts test/web-phone.test.ts alone on origin/main b27bb0c3 and on this branch BASELINE exit 1 130s ▶ check.yml gate (1) pull_request (fail) egress, reported: an undeclared public host goes through and is counted; a private one never does > an undeclared public host is let through and counted [1.38ms] [shard 3] Ran 112 tests across 3 files in 14.4s; 1 failed. classify: reran test/contain.test.ts alone on origin/main b27bb0c3 and on this branch BASELINE exit 1 15s ▶ check.yml gate (2) pull_request Ran 1336 tests across 137 files in 273.9s (137 shards). ok exit 0 274s ▶ check.yml gate (3) pull_request Ran 1508 tests across 138 files in 198.5s (138 shards). ok exit 0 199s ``` </details>
A step that runs without a cell ends with everything it started, on a cancel, a timeout, or its own exit
All checks were successful
check / gate (3) (pull_request) Successful in 4m8s
check / gate (2) (pull_request) Successful in 5m27s
check / gate (1) (pull_request) Successful in 23m50s
check / check (pull_request) Successful in 1s
preview / preview (pull_request) Has been skipped
82c7d25321
Where the host cannot hold a cell (CI, a pod without bwrap), an install,
test or check step ran as a plain `/bin/sh -c` child in the forge's own
process group, and a cancel or timeout SIGKILLed the shell alone. What
the shell had started (`bun install`'s scripts, a test runner and its
workers, a `sleep`) was re-parented and kept running in the checkout
after the step was reported cancelled; a superseded check kept burning
the machine beside its replacement.

The step now leads a process group of its own (`detached`, without a
cell only), a cancel or timeout kills the group, and once the shell has
exited, what is left of the group is killed. A cell is unchanged: its
tree dies with bubblewrap. `signalGroup` moves to module level, shared
with `startProcess` (#63).

Since a terminal's Ctrl-C no longer reaches a running check, the forge
stops checks without the roll's drain wait on a person's signal
(SIGINT, SIGHUP): they are cut off and queued again at once. A SIGTERM
(a release) still lets them finish within FORGE_CHECKS_DRAIN_S.
Sign in to join this conversation.
No reviewers
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
trevato-apps/forge!64
No description provided.