Public: anyone can read this; nobody can change it.

An app that runs without a cell stops with everything it started, and leaves no child running once it has gone #63

Merged opened this view on origin

Check · Review · Land done · Live — Landed; nothing deploys from this branch, so this is the end of its path.

Merged

Where the host cannot hold a cell, an app ran as a plain child in the forge's own process group, and stop() signalled its pid alone. A child it had started (a worker, `bun run`'s script, a `sleep`) was re-parented and kept running on the environment's data and token after the forge called it stopped; one that outlived a crash ran beside the restart.

shipped merged

opened this view on origin

Where the host cannot hold a cell, an app ran as a plain child in the forge's own process group, and stop() signalled its pid alone. A child it had started (a worker, bun run's script, a sleep) was re-parented and kept running on the environment's data and token after the forge called it stopped; one that outlived a crash ran beside the restart.

The app now leads a process group of its own (detached, without a cell only), stop() asks and then kills the group, and once the app has exited, however it went, what is left of the group is killed. A cell is unchanged: its tree is bubblewrap's.

Since a terminal's hangup no longer reaches those apps directly, the forge takes SIGHUP the way it takes Ctrl-C: it drains and stops them.

test/apps-stop-tree.test.ts: red on main (3 of 3), green here.

Regression test

test/apps-stop-tree.test.ts (3 tests: a stop ends the app's children; a child that ignores SIGTERM is killed once the app has gone; an app that dies on its own takes its children with it). Pinned bun 1.3.14:

  • origin/main dc68048e source + this test: 0 pass, 3 fail (each child still alive 3 s after the stop/exit)
  • this branch: 3 pass, 0 fail

Also on the box, CI-shaped PATH (no bwrap, so every app runs uncelled — the path this changes): all 24 gated test/apps*.test.ts + app.test.ts + standing-every-process.test.ts: 212 pass, 0 fail.

SIGHUP: the forge started in a pty, the pty then killed: this branch logs forge draining … why=SIGHUP and exits; main dies on the hangup without draining.

Security

Process control. The group signal is only ever -proc.pid of a group this spawn just made the app lead (detached, i.e. setsid), never the forge's own group, and a cell's spawn is unchanged. Linux keeps a pid from reuse while it is any live process's pgid, so the kill after exit reaches only what is left of the app's group, or nothing (ESRCH, ignored). What it closes: an app's children outliving a stop or a crash, still running on the environment's data dir and forge token after the forge recorded it stopped (or its token revoked) — and beside its restart. An app that calls setsid itself still escapes, as before; a cell is the containment for that.

<!-- pipeline:validation -->

Validation

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

stepresulttime
bun install --frozen-lockfileok (exit 0)0s
forge.json check.fastBASELINE (exit 1)121s
check.yml gate (1) pull_requestBASELINE (exit 1)14s
check.yml gate (2) pull_requestok (exit 0)270s
check.yml gate (3) pull_requestok (exit 0)190s
  • ⚠️ 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.24ms] [shard 10]
Ran 2986 tests across 296 files in 106.7s; 1 failed.
classify: reran test/contain.test.ts test/usage.test.ts test/web-code.test.ts  alone on origin/main dc68048e and on this branch
  BASELINE  exit 1  121s
▶ 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 [3.03ms] [shard 3]
Ran 112 tests across 3 files in 14.2s; 1 failed.
classify: reran test/contain.test.ts  alone on origin/main dc68048e and on this branch
  BASELINE  exit 1  14s
▶ check.yml gate (2) pull_request
Ran 1334 tests across 136 files in 269.3s (136 shards).
  ok  exit 0  270s
▶ check.yml gate (3) pull_request
Ran 1501 tests across 136 files in 190.4s (136 shards).
  ok  exit 0  190s
Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs