Skip to content

fix(session): detach background daemons from launcher groups - #26

Merged
kitlangton merged 2 commits into
mainfrom
fix/daemon-detach-maintenance
Sep 2, 2026
Merged

fix(session): detach background daemons from launcher groups#26
kitlangton merged 2 commits into
mainfrom
fix/daemon-detach-maintenance

Conversation

@kitlangton

@kitlangton kitlangton commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Why

Sending SIGHUP to the process group that launched termctrl start also kills the background session daemon. The application becomes unreachable even though named sessions are intended to remain available across CLI invocations. Restarted sessions have the same problem because both paths use start_locked.

What Changes

Operation Before After
Start a named session, then send SIGHUP to its launcher group status fails with connection refused status remains running and show returns the application's screen
Restart a named session, then send SIGHUP to the new launcher group The replacement daemon is also killed The replacement session remains available; the prior application has exited
Explicitly stop the surviving session Not reachable after the hangup stop removes the socket and terminates the application

Call setsid in the Unix background daemon's pre_exec hook, propagating failures through the existing spawn error. The daemon becomes its own session leader before exec.

The CLI regressions use an explicitly isolated launcher group, assert it is not the test runner's group, and signal only that owned group. They verify daemon session leadership, status, visible output, restart cleanup, and explicit stop. Cleanup retains every generation's PID record and verifies UID, executable, and the unique fixture argument before fallback signals to individual processes. A third regression deliberately fails the old-generation exit assertion, verifies both generations are cleaned up, and confirms stale PID records do not terminate an independently owned sentinel session.

Scope

Related: #18. Extracts only its background-daemon detachment fix and regression intent; tape playback and pointer automation remain separate. Foreground run, cursor negotiation, Windows support, and the control protocol are unchanged.

Final review covers 4de9fec93aac920e60d355e5f6a8cfad54fba496..5f4f2e3ad4868421a7d92219bf4540c236980a17, rebased onto main after #25 merged. The CPR files match that base; the diff contains only the background-daemon hook, integration tests, and Changeset.

Includes a patch Changeset for the fixed six-package npm group. No versions, lockfiles, tags, or published artifacts are changed.

Verification

Local validation ran on macOS arm64, with Rust 1.95.0 and Zig 0.15.2. Both Linux CI runs passed before merge, including the new integration regressions.

export PATH="/opt/homebrew/opt/zig@0.15/bin:$PATH"
bun install --frozen-lockfile
cargo fmt --all -- --check
cargo test --locked --test daemon_detach -- --nocapture
cargo test --locked --all-targets
cargo clippy --locked --all-targets --all-features -- -D warnings
cargo build --locked --release
cargo test --locked --release --test daemon_detach -- --nocapture
bun run test:npm
bun run build:npm
bun run validate:npm
bun run changeset status --since origin/main
cargo package --locked --list
cargo package --locked
  • Red: before the production fix, both CLI regressions failed with connection refused after launcher-group SIGHUP.
  • Green: the final rebased full-suite retry passed 121 Rust tests, with one manual benchmark ignored. Clippy and formatting checks passed. Independent final focused checks passed all three integration tests in both debug and release profiles.
  • Real PTY evidence: the release-built CLI survives the owned launcher-group hangup for both start and restart; status stays running, show returns READY, and stop removes the socket and application.
  • Failed-generation cleanup: five debug and five release repeats of the cleanup regression left none of their 90 fixture PIDs or runtime directories behind. Independent final debug/release suite runs confirmed both generations were removed after the caught assertion failure, stale-record sentinel sessions remained running until their own teardown, and all 36 recorded fixture PIDs were absent afterward.
  • npm: 12 client tests and 9 OpenTUI adapter tests passed; builds passed; packed client/native artifacts validated in clean Bun and Node/Vitest consumers on macOS arm64.
  • Changesets reports exactly six patch releases, with no minor or major release.
  • Crate packaging and verification passed. The repository's existing explicit package include list excludes the integration-test file from the published crate; it still runs in repository CI.
  • Fixture processes and runtime directories were cleaned up.

The first full-suite run transiently failed natural_parent_exit_terminates_pty_holding_descendants at src/session.rs:2297. Its solo rerun, 20 focused repeats, and full-suite retry passed; an independent final solo rerun also passed. This test calls Session::start directly, not the named-session start_locked daemon-launch path, so it cannot reach the new setsid hook. finish_exited_output waits for PTY EOF rather than disappearance of the orphan descendant PID; the immediate kill(pid, 0) assertion can race orphan reaping. That is a plausible timing explanation, not a confirmed diagnosis of the transient failure.

An additional existing release check was run:

bun run --cwd packages/opentui release:check

It fails locally under Node 26.5.0/npm 11.17.0 because its npm publish --dry-run rejects the already-published version 1.1.0: You cannot publish over the previously published versions: 1.1.0. The release script and package version are unchanged in this PR. No publish was performed. This is separate from the passing build, test, packed-consumer, and crate checks above.

A successful stop only accounts for the current generation. Preserve and snapshot all generation records, then verify process ownership before fallback cleanup, including failed restart predecessors.
@kitlangton
kitlangton merged commit 96c90e6 into main Sep 2, 2026
2 checks passed
@kitlangton
kitlangton deleted the fix/daemon-detach-maintenance branch September 2, 2026 03:37
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