Skip to content

Release v0.10.1 with descendant cleanup fix - #12

Merged
pasunboneleve merged 7 commits into
mainfrom
feature/process-death-guard
Aug 26, 2026
Merged

Release v0.10.1 with descendant cleanup fix#12
pasunboneleve merged 7 commits into
mainfrom
feature/process-death-guard

Conversation

@pasunboneleve

@pasunboneleve pasunboneleve commented Aug 26, 2026

Copy link
Copy Markdown
Owner

Summary

  • guard every managed process and hook with a Rust companion and private parent-death channel
  • kill children, grandchildren, and deeper descendants that remain in the managed process group when devloop disappears
  • keep the guardian identity and executable image stable across name-based signals and installation replacement
  • release the fix as v0.10.1 with distinct Linux and macOS required-check identities

Design

Devloop keeps one endpoint of a private Unix socket. A separate Rust guardian watches the other endpoint outside the managed target group. If devloop disappears, socket EOF makes the guardian kill and reap the target group. Linux executes a pinned companion descriptor; macOS materializes the pinned bytes in an owner-only temporary executable through spawn, then unlinks it.

A descendant that deliberately creates a new session with setsid() escapes portable POSIX process-group containment. Ordinary child, grandchild, and deeper subprocess trees remain covered.

Release

  • Cargo package and lockfile version: 0.10.1
  • changelog section and release-notes input: 0.10.1, dated 2026-08-26
  • intended tag: v0.10.1
  • release archives contain both devloop and devloop-process-guardian

Validation

  • cargo fmt --check
  • cargo test — 163 unit tests and six integration/smoke tests passed
  • cargo clippy --all-targets --all-features -- -D warnings
  • RUST_LOG=info ./scripts/ci-smoke.sh
  • cargo build --release --locked --bins
  • release-notes extraction, shell syntax, YAML parse, archive contents, version output, and git diff --check
  • real Live Dashblog reproduction previously proved that killing only devloop removes Wrangler, esbuild, and Workerd descendants and releases port 8787

Roborev job 199 (Grok) reviewed the release/CI diff and found no issues. Earlier implementation reviews 196 and 198 found four valid medium issues; all were fixed and regression-tested. Their verdicts remained F, and the bounded review policy prohibited a third implementation review.

Context:
An abruptly terminated devloop could not run its normal group shutdown,
leaving Wrangler, Workerd, and other nested descendants alive.

Decision:
Launch every managed process and hook through one guarded Unix process
group. A private control socket makes the guardian kill that group when
devloop disappears, while foreground execution preserves stdin and the
existing TERM grace period.

Alternatives considered:
Process-management crates cover explicit shutdown or Drop but not the
full descendant tree after SIGKILL on both Linux and macOS. Cgroups and
systemd supervision are Linux-specific and external to devloop.

Tradeoffs:
Each external command gains a small /bin/sh guardian and watcher. A
descendant that deliberately creates a new session can still escape
portable Unix process-group containment.

Architectural impact:
External command ownership now passes through one GuardedProcess spawn
boundary shared by processes and hooks. Abrupt-death, graceful TERM,
stdin, and three-level descendant behavior have deterministic coverage.

SemVer:
Patch. Record the behavior under Unreleased without a version bump or a
dated release section.
Context:
The shell trap wrapper made descendant cleanup depend on shell signal and file-descriptor behavior. A Rust re-exec under the devloop process identity also exposed the guardian to name-based kills and could load a different protocol after an in-place update.

Decision:
Ship a separate Rust guardian companion for every managed process and hook. Devloop pins the companion inode at run startup, passes parent lifetime over a private socket, and keeps the target in its own process group. The guardian survives terminal-oriented signals, restores normal signal handling for the target, reports startup failures through a bounded protocol, and bounds shutdown waits.

Alternatives considered:
Systemd scopes and Linux cgroups were rejected because cleanup must remain devloop-owned and work on macOS. Client shell wrappers were rejected because they cannot enforce cleanup for every configured command. Re-executing devloop itself was rejected because it shares the supervisor process identity and can drift across installation updates.

Tradeoffs:
The companion executable must remain beside devloop and release archives now contain both files. Portable process groups still cannot contain descendants that deliberately create a new session; those commands must provide their own shutdown integration.

Architectural impact:
ProcessManager now receives one pinned GuardianExecutable from Engine. The shared external-command spawn boundary owns the guardian protocol, process groups, startup diagnostics, and bounded termination. Release packaging and installation documentation preserve the two-binary runtime contract.

SemVer:
PATCH. This fixes cleanup and failure handling without changing the public CLI. The change remains in Unreleased with no version bump or dated release section.
Context:
GitHub macOS CI proved that Darwin rejects execution through /dev/fd with EACCES, even though Linux can execute the same pinned image through /proc/self/fd.

Decision:
Keep the guardian image open for the devloop run. Execute a duplicated descriptor on Linux, while macOS copies the pinned bytes into an owner-only temporary executable for each spawn and unlinks it immediately after spawn completes.

Alternatives considered:
Reopening the installed companion path would lose protocol consistency during path replacement. A shell wrapper would reintroduce the lifecycle and portability boundary this Rust companion replaced.

Tradeoffs:
macOS performs one bounded companion-image copy per managed spawn. Linux retains its zero-copy descriptor path.

Architectural impact:
GuardianExecutable now prepares a platform-specific GuardianInvocation that owns every resource required only through Command::spawn. Managed-process supervision, signal handling, and target process groups remain unchanged.

SemVer: PATCH
Context:
GitHub restored an older target/debug/devloop from the Cargo cache. The smoke script treated that executable path as sufficient and tested a stale supervisor against the current companion.

Decision:
Always run cargo build --bins before the runtime smoke launches devloop, so every run exercises binaries produced from the checked-out revision.

Alternatives considered:
Deleting the cached binary would depend on cache layout and discard useful incremental artifacts. Timestamp checks would duplicate Cargo dependency logic.

Tradeoffs:
The smoke step always pays Cargo's incremental build check and compiles changed binaries when needed.

Architectural impact:
Product runtime behavior is unchanged. The CI smoke boundary now establishes build provenance before behavioral validation.

SemVer: none; validation-only change
Context:
The descendant-cleanup fix is complete and validated on Linux and macOS, but the repository still reports version 0.10.0 and exposes ambiguous CI status names that cannot safely protect main.

Decision:
Release the fix as patch 0.10.1, finalize its changelog section, keep Unreleased empty, and give the Linux and macOS caller jobs distinct required-check identities.

Alternatives considered:
A minor release would overstate a backwards-compatible bug fix. Requiring the shared rust / rust context would not prove that both supported platforms passed.

Tradeoffs:
Renaming the checks requires one fresh PR matrix before the ruleset can require the new contexts.

Architectural impact:
Runtime architecture is unchanged. Release metadata now identifies 0.10.1, while repository policy can independently require Linux and macOS validation.

SemVer: PATCH
@pasunboneleve pasunboneleve changed the title Fix descendant cleanup after supervisor death Release v0.10.1 with descendant cleanup fix Aug 26, 2026
@pasunboneleve
pasunboneleve merged commit b15180a into main Aug 26, 2026
2 checks passed
@pasunboneleve
pasunboneleve deleted the feature/process-death-guard branch August 26, 2026 05:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant