8000 T31 — Keep terminal and detached execs past a client loss, stop one exec, and honour exec_enabled by minimal-foundry[bot] · Pull Request #2874 · gominimal/minimal · GitHub
[go: up one dir, main page]

Skip to content

T31 — Keep terminal and detached execs past a client loss, stop one exec, and honour exec_enabled - #2874

Merged
mitodrummer merged 11 commits into
mainfrom
agent/2406-keep-terminal-and-detached-execs-past-a-client-l
Nov 4, 2026
Merged

mitodrummer merged 11 commits into
mainfrom
agent/2406-keep-terminal-and-detached-execs-past-a-client-l

Conversation

@minimal-foundry
@minimal-foundry minimal-foundry Bot commented Nov 3, 2026 •
Copy link
Copy Markdown
Contributor

T31 — Keep terminal and detached execs past a client loss, stop one exec, and honour exec_enabled

slice: S7
kind: task
covers: BOX-150, BOX-151, BOX-152, BOX-153
size: M
est: 220 code, 260 tests
executor: agent
difficulty: needs care
model: standard
diagnostics: daemon log names exec stops and refusals
observability: exec events carry argv, flag and code
layers:
- exec:
- crates/minimald/src/exec.rs: survival; exec_enabled refusal
- crates/minimald/src/exec/process.rs: stops one exec
- crates/minimald/src/nsenter.rs: tty exec PTY is its controlling terminal
- crates/minimald/src/exec/tests.rs: tests
test: cargo nextest run -p minimald pty_or_detached_exec_survives_client_loss
test: cargo nextest run -p minimald stop_exec_ends_process_group
test: cargo nextest run -p minimald exec_disabled_refuses_exit5
test: cargo nextest run -p minimald tty_exec_pty_is_controlling_terminal
- events:
- crates/boxes/src/events.rs: exec event kinds
- crates/minimald/src/rpc.rs: exec stop handler
test: cargo nextest run -p minimald exec_events_carry_principal_argv_pty_code
produces: none
consumes:
- crates/boxes/src/events.rs
proof: A terminal exec survives its client; stopping one leaves the box.
depends on:
- blocked by #2404
- blocked by #2389

Proof

A tty or detached exec keeps running after its client goes away. A later attach, output read or wait by its exec id still reaches it. A StopExec request ends that one exec (SIGTERM to its process group, a wait, then SIGKILL) and leaves the box and its other execs running. A box whose stored spec sets [io] exec_enabled = false refuses every command with exit 5 and names the key. A tty exec's PTY is its controlling terminal. exec_started and exec_exited events carry the principal, argv, PTY flag and exit code.

Notes from the agent: 1) PTY and detached survival was already in the tree. This task adds only the test for it. 2) The StopExec RPC and its request and response types live in rpc.rs because the layers do not name crates/minimald-rpc. A client (min) cannot call the RPC until a later task moves the types into minimald-rpc and adds a command. 3) A stopped exec's exec_exited reason is 'exited'. A 'stopped' reason needs a new ExecExitReason variant in exec/bridge.rs, which is outside the layers. The daemon log has 'exec stop requested' and 'exec stopped' lines with the exec id. 4) exec_enabled is read raw from spec.json, in the same way as stored_spec_sets_pty. mfile::spec::BoxSpec has no exec_enabled field and uses deny_unknown_fields. So a box created from a spec with that key fails the BoxSpec parse at launch until mfile gets the field (outside the layers). The e2e test writes spec.json directly for this reason. 5) The refusal text is: 'box does not run commands: its spec sets [io] exec_enabled = false.' Exit 5 applies to every exec form (shell, argv, attached, tty and detached exec). 6) tty_exec_pty_is_controlling_terminal tests nsenter::lead_session_on_terminal, the pre-exec step that the shim runs when its stdin is a tty. No test runs the shim binary end to end. 7) Principal is the SSH login user of the connection (ssh_username). 8) open_terminal in exec/process.rs is now pub(super) so that the test can use it. 9) Beyond the test lines, I r…[truncated]

Tests

line exit tail
cargo nextest run -p minimald pty_or_detached_exec_survives_client_loss 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-0617... Compiling paths v0.0.1 (/workbench/crates/paths) Compiling args v0.0.1 (/workbench/crates/args) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling client-config v0.0.1 (/wo
cargo nextest run -p minimald stop_exec_ends_process_group 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-a526... Compiling stdlib v0.0.22 (/workbench/crates/stdlib) Compiling version v0.8.0 (/workbench/crates/version) Compiling decode v0.0.1 (/workbench/crates/decode) Compiling mlog v0.
cargo nextest run -p minimald exec_disabled_refuses_exit5 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-6457... Compiling stdlib v0.0.22 (/workbench/crates/stdlib) Compiling version v0.8.0 (/workbench/crates/version) Compiling decode v0.0.1 (/workbench/crates/decode) Compiling mlog v0.
cargo nextest run -p minimald tty_exec_pty_is_controlling_terminal 0 Applying loadouts: default Running task runtime-implement-test-3 in session task-runtime-implement-test-3-0785... Compiling stdlib v0.0.22 (/workbench/crates/stdlib) Compiling version v0.8.0 (/workbench/crates/version) Compiling decode v0.0.1 (/workbench/crates/decode) Compiling mlog v0.
cargo nextest run -p minimald exec_events_carry_principal_argv_pty_code 0 Applying loadouts: default Running task runtime-implement-test-4 in session task-runtime-implement-test-4-f19d... Compiling stdlib v0.0.22 (/workbench/crates/stdlib) Compiling version v0.8.0 (/workbench/crates/version) Compiling decode v0.0.1 (/workbench/crates/decode) Compiling mlog v0.

Verify

min task run agent-verify passed ([tasks.verify]).

Closes #2406

Residuals

Out-of-layer edit accepted by the maintainer (2026-11-04): crates/minimald/tests/nsenter_integration.rs runs the real nsenter shim for the tty-exec case the maintainer asked for.

A stop ends one exec through a StopExec RPC: SIGTERM to its process
group through the injection shim, a wait, then SIGKILL. The box and
its other execs keep running (BOX-151).

A box whose stored spec sets [io] exec_enabled = false refuses every
command with exit 5, and the refusal names the key (BOX-153).

The shim makes a tty exec lead a new session with its PTY as the
controlling terminal. exec_started and exec_exited events now carry
the principal and the argv beside the PTY flag and the exit code
(BOX-152). A test pins that PTY and detached execs survive the loss
of their client (BOX-150).
@coderabbitai
coderabbitai Bot commented Nov 3, 2026 •
Copy link
Copy Markdown
Contributor

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 06b0b389-95fc-4885-adf8-885c3586ecf6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

@minimal-foundry
minimal-foundry Bot commented Nov 3, 2026 •
Copy link
Copy Markdown
Contributor Author

Reviewed: 2 findings, none blocking at b8edbaac2c07.

Address the comments on the diff.

Clobber: not checked with git log. If that was not intentional, restore the earlier change.

@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minimal-review — advisory review

Verdict COMMENT — 2 findings, 2 inline.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

Comment thread crates/minimald/src/exec/process.rs Resolved
Comment thread crates/minimald/src/exec/process.rs Resolved
@minimal-arch minimal-arch Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arch-review: CONDITIONS · 4 conditions · the full ruling

The stop of one exec does not match line 497 of architecture.md. A tty exec now gets a controlling terminal, so a shell job can move to its own process group and survive the stop. C1 fixes this. An attached exec cannot be stopped at all, and C3 fixes this. The exec_enabled check lets the exec run when the stored spec cannot be read or parsed. C2 makes it refuse. File the two residuals in the PR body as issues and link them (C4).

Ruled at 3f85533 against gominimal/arch@7a86ec7bb3c0 by claude-opus-5-5.

Comment thread crates/minimald/src/nsenter.rs Resolved
Comment thread crates/minimald/src/exec/process.rs Resolved
Comment thread crates/minimald/src/exec/process.rs Resolved
Comment thread crates/minimald/src/rpc.rs Resolved
A stored spec that cannot be read or does not parse as JSON now
refuses the exec with exit 5, in place of letting it run. The refusal
says the spec cannot be read, and the daemon log names the cause.
@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minimal-review — advisory review

Verdict COMMENT — 1 finding, 1 inline.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

Comment thread crates/minimald/src/exec/process.rs Resolved
@minimal-arch minimal-arch Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arch-review: CONDITIONS · 3 conditions · the full ruling

The exec_enabled gate now fails closed, and the events carry the fields that line 497 names. Two gaps remain in the stop of one exec (BOX-151). A tty exec now leads a session with the PTY as its terminal, so a shell runs jobs in other process groups. The shim kills only the first group, so those jobs survive a stop. C1 fixes this. A plain attached exec has an id but cannot be stopped, and the RPC says that it does not exist. C2 fixes this. C3 files the client path and the mfile field as residuals with issues.

Ruled at a8e3fe7 against gominimal/arch@7a86ec7bb3c0 by claude-opus-5-5.

Comment thread crates/minimald/src/nsenter.rs Resolved
Comment thread crates/minimald/src/exec/process.rs Resolved
Comment thread crates/minimald/src/rpc.rs Resolved
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 1 of 10 on the runtime's review findings, pushed as a8e3fe71bb16.

line exit tail
cargo nextest run -p minimald pty_or_detached_exec_survives_client_loss 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-3bdf... Compiling paths v0.0.1 (/workbench/crates/paths) Compiling args v0.0.1 (/workbench/crates/args) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling client-config v0.0.1 (/wo
cargo nextest run -p minimald stop_exec_ends_process_group 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-d0e2... Compiling stdlib v0.0.22 (/workbench/crates/stdlib) Compiling version v0.8.0 (/workbench/crates/version) Compiling decode v0.0.1 (/workbench/crates/decode) Compiling mlog v0.
cargo nextest run -p minimald exec_disabled_refuses_exit5 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-a810... Compiling stdlib v0.0.22 (/workbench/crates/stdlib) Compiling version v0.8.0 (/workbench/crates/version) Compiling decode v0.0.1 (/workbench/crates/decode) Compiling mlog v0.
cargo nextest run -p minimald tty_exec_pty_is_controlling_terminal 0 Applying loadouts: default Running task runtime-implement-test-3 in session task-runtime-implement-test-3-0303... Compiling stdlib v0.0.22 (/workbench/crates/stdlib) Compiling version v0.8.0 (/workbench/crates/version) Compiling decode v0.0.1 (/workbench/crates/decode) Compiling mlog v0.
cargo nextest run -p minimald exec_events_carry_principal_argv_pty_code 0 Applying loadouts: default Running task runtime-implement-test-4 in session task-runtime-implement-test-4-3115... Compiling stdlib v0.0.22 (/workbench/crates/stdlib) Compiling version v0.8.0 (/workbench/crates/version) Compiling mlog v0.0.1 (/workbench/crates/mlog) Compiling decode v0.0.

Verify min task run agent-verify passed.

Review threads the coder judged:

  • 👍 valid — PRRT_kwDOPdfcxM6zWGmH: exec_gate() now fails closed: a stored spec that cannot be read or does not parse refuses the exec with exit 5 and its own message. The test pins both cases. (not posted on the thread: not an open thread on the pull request)
  • 👎 invalid — PRRT_kwDOPdfcxM6zWGmO: The spec (BOX-149, BOX-152) defines only the reasons exited and timeout, and BOX-152 requires the exit code, not a stop reason. The person who stops an exec gets the StopExec answer, and the daemon log records "exec stopped". A new reason needs a variant and its parser in crates/minimald/src/exe (not posted on the thread: not an open thread on the pull request)

A tty or detached exec keeps running after its client disconnects. A person can stop one exec by its id, and the box stays running. A box whose stored spec sets [io] exec_enabled = false refuses commands with exit 5. A box whose stored spec cannot be read or parsed now also refuses.

Notes from the agent: Revise round 1. ExecBox::exec_disabled() is now exec_gate(), and it returns ExecGate { Enabled, Disabled, SpecUnreadable }. A spec that cannot be read or parsed refuses with exit 5 and its own message: "box does not run commands: its stored spec cannot be read, so the daemon cannot check [io] exec_enabled." The message gives no next step, because no command repairs a stored spec. A spec that parses but has a non-bool exec_enabled still leaves exec on, because only false disables it. I did not add a distinct stop reason (thread 2). The reason for that is in the thread note.

@mitodrummer
Copy link
Copy Markdown
Member

/revise

Four fixes on a8e3fe71, all in this task's layers (architecture.md line 497: any exec ends on min box stop <box> --exec <id>, and a stop ends every child that the command did not detach into its own session):

  1. Stop a tty exec's whole session (arch C1). Now that a tty exec leads its own session and owns the PTY (nsenter.rs ~844), a shell on it uses job control, and each job gets its own process group. kill_group/term_group_once signal only -pgid, so a stop or a timeout misses those jobs. Signal every process whose session id is the exec's pid, or every process group of that session, in exec/process.rs. Add a test where a tty shell backgrounds a job into its own group and StopExec ends both.
  2. Register attached execs for stop and wait (arch C2, and the maintainer's note on T31 — Keep terminal and detached execs past a client loss, stop one exec, and honour exec_enabled #2406). find_exec (exec/process.rs ~1395) knows only tty and detached execs, because only start_managed_exec calls register_exec. A live plain (attached) exec has an id, but StopExec (rpc.rs ~1509) answers that the box has no such exec, which is false. Register the attached exec in run_attached_exec, so the lookup finds it, and keep the "no such exec" answer for an id this daemon never started in the box.
  3. Scrub argv before it reaches the events file (minimal-review HIGH, exec/process.rs ~1114). exec_started/exec_exited store the raw argv, so curl -H 'Authorization: Bearer …' writes the token to disk and to every events reader. Apply diagnostics::redact::scrub_secrets as the exec log lines already do (exec.rs ~226 and ~724), and test it with a bearer-token argv.
  4. Bound the detached output pump (maintainer's note on T31 — Keep terminal and detached execs past a client loss, stop one exec, and honour exec_enabled #2406). supervise_managed_exec (exec/process.rs ~1249) never aborts a detached exec's pump. If a descendant keeps the pipe open after the exec ends, the pump task, its descriptor and its transcript file live for the daemon's lifetime. Abort or bound the pump when the exec ends (a short drain), or at the latest when the box is reaped, and do not leave the spawn_blocking read parked.

Already fixed at this head: the fail-closed exec_enabled gate. Arch C4/C3 (move StopExec into minimald-rpc and add min box stop --exec) is the box CLI's work; the maintainer links that issue once it exists.

A stop or a timeout of a tty exec now ends every process group of the
exec's session, so a job a shell put in its own group ends too. An
attached exec is registered by its id, so a stop or a wait finds it.
The exec events store argv with its secrets masked. The output pump of
a detached exec stops at the drain after the exec ends. The exec gate
refuses a stored spec that is not a JSON object.
@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minimal-review — approval withheld

Verdict COMMENT — APPROVE withheld: 8 unresolved threads — minimal-arch[bot] crates/minimald/src/nsenter.rs:845, minimal-arch[bot] crates/minimald/src/exec/process.rs:1072, minimal-arch[bot] crates/minimald/src/exec/process.rs:1431, minimal-arch[bot] crates/minimald/src/rpc.rs:1446, minimal-foundry[bot] crates/minimald/src/exec/process.rs:1117, …. No findings.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

@minimal-arch minimal-arch Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arch-review: CONDITIONS · 2 conditions · the full ruling

The compare fixes the stop of a tty exec, the stop of an attached exec and the fail-closed gate, and those parts conform. One new item deviates. The exec events now store a masked argv. architecture.md line 497 says: "The argv is retained as typed". The BoxRead gate on the stream and the min secret rule are what protect it. C1 restores the argv as typed in the events. C2 names an issue for the mfile exec_enabled field and the min box stop --exec client, as residuals in the PR body.

Ruled at 60a5c0e against gominimal/arch@7a86ec7bb3c0 by claude-opus-5-5.

Comment thread crates/minimald/src/exec/process.rs Resolved
Comment thread crates/minimald/src/exec/process.rs Resolved
Comment thread crates/minimald/src/exec/tests.rs Resolved
Comment thread crates/minimald/src/exec/process.rs Resolved
@mitodrummer
Copy link
Copy Markdown
Member

/revise

Two changes on 60a5c0ee, from arch's ruling on this head. The maintainer chose arch's reading over the earlier argv-mask request.

  1. Store the exec argv as typed (arch C1). architecture.md line 497 and the owner ruling gominimal/arch#76 keep an exec's argv as typed in the box's event stream. BoxRead gates who reads that stream. In crates/minimald/src/exec/process.rs, ExecBox::write_event stores facts.argv under exec_data::ARGV, one JSON string per word, in place of scrubbed_argv(&facts.argv) (~line 1112). Remove scrubbed_argv (~line 1170). In crates/minimald/src/exec/tests.rs exec_events_carry_principal_argv_pty_code, replace the masked-argv block (from ~line 4062) with an assertion that both events store the argv exactly as the request sent it. Keep scrub_secrets on the daemon log lines, which are the log, not the box's event stream.
  2. Name the residuals in the PR body (arch C2). Add a ## Residuals section:

architecture.md line 497 and gominimal/arch#76 keep the argv as typed in
the box's event stream, behind the BoxRead gate. Remove scrubbed_argv.
The daemon log still masks the argv with scrub_secrets.
@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minimal-review — advisory review

Verdict COMMENT — 1 finding, 1 inline.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

Comment thread crates/minimald/src/exec/process.rs Resolved
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 3 of 10 on a /revise note, pushed as 95c66b352324.

line exit tail
cargo nextest run -p minimald pty_or_detached_exec_survives_client_loss 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-437e... Compiling paths v0.0.1 (/workbench/crates/paths) Compiling args v0.0.1 (/workbench/crates/args) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling client-config v0.0.1 (/wo
cargo nextest run -p minimald stop_exec_ends_process_group 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-a33e... Compiling stdlib v0.0.22 (/workbench/crates/stdlib) Compiling version v0.8.0 (/workbench/crates/version) Compiling decode v0.0.1 (/workbench/crates/decode) Compiling mlog v0.
cargo nextest run -p minimald exec_disabled_refuses_exit5 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-829c... Compiling stdlib v0.0.22 (/workbench/crates/stdlib) Compiling version v0.8.0 (/workbench/crates/version) Compiling decode v0.0.1 (/workbench/crates/decode) Compiling mlog v0.
cargo nextest run -p minimald tty_exec_pty_is_controlling_terminal 0 Applying loadouts: default Running task runtime-implement-test-3 in session task-runtime-implement-test-3-45a8... Compiling stdlib v0.0.22 (/workbench/crates/stdlib) Compiling version v0.8.0 (/workbench/crates/version) Compiling decode v0.0.1 (/workbench/crates/decode) Compiling mlog v0.
cargo nextest run -p minimald exec_events_carry_principal_argv_pty_code 0 Applying loadouts: default Running task runtime-implement-test-4 in session task-runtime-implement-test-4-1aab... Compiling stdlib v0.0.22 (/workbench/crates/stdlib) Compiling version v0.8.0 (/workbench/crates/version) Compiling mlog v0.0.1 (/workbench/crates/mlog) Compiling decode v0.0.

Verify min task run agent-verify passed.

Review threads the coder judged:

  • 👍 valid — crates/minimald/src/nsenter.rs:845: Fixed in ee5588f: a stop or a timeout of a tty exec signals every live process whose session id is the exec's pid (nsenter.rs signal of session sid).
  • 👍 valid — crates/minimald/src/exec/process.rs:1072: Fixed earlier: exec_gate returns SpecUnreadable (refuse, exit 5) for a read error and for bytes that are not a JSON object. NotFound and a missing key still allow exec.
  • 👍 valid — crates/minimald/src/exec/process.rs:1405: Fixed in ee5588f: run_attached_exec registers an ExecEntry and a stop token, so find_exec and stop_exec find an attached exec.
  • 👍 valid — crates/minimald/src/rpc.rs:1446: Issue T16 — See, read, wait on and stop one exec #2913 (BCLI T16) owns the minimald-rpc move of StopExec and the min box stop --exec client. The result notes ask for it in the PR body Residuals section.
  • 👎 invalid — crates/minimald/src/exec/process.rs:1114: The maintainer ruled for arch's reading (architecture.md line 497, gominimal/arch#76): the event stream keeps the argv as typed, and BoxRead gates who reads it. The daemon log keeps scrub_secrets.
  • 👍 valid — crates/minimald/src/nsenter.rs:844: Fixed in ee5588f: kill and the timeout TERM now signal every process of the exec's session, not only -pgid, so job-control groups end too.
  • 👍 valid — crates/minimald/src/exec/process.rs:1405: Fixed in ee5588f: run_attached_exec calls register_exec and register_stop, so find_exec returns the live attached exec.
  • 👍 valid — crates/minimald/src/rpc.rs:1509: Fixed with the attached-exec registration: the 'has no exec' answer now prints only for an id that this daemon did not start in the box.
  • 👍 valid — crates/minimald/src/exec/process.rs: write_event now stores facts.argv as typed, one JSON string per word.
  • 👍 valid — crates/minimald/src/exec/process.rs: Removed scrubbed_argv. The daemon log lines keep scrub_secrets.
  • 👍 valid — crates/minimald/src/exec/tests.rs: The test now asserts that both events store the argv exactly as the request sent it.
  • 👍 valid — crates/minimald/src/exec/process.rs:1158: Named BOX: add [io] exec_enabled to mfile's IoSpec so a box can turn exec off (BOX-153 residual of T31) #2973 (mfile IoSpec.exec_enabled) as a residual for the PR body Residuals section.

A tty or detached exec keeps running after its client disconnects. A stop of one exec ends only that exec, and the box and its other execs keep running. The exec_started and exec_exited events store the argv exactly as the request sent it.

Notes from the agent: This round stores facts.argv as typed under exec_data::ARGV and removes scrubbed_argv. The daemon log lines keep scrub_secrets. The test exec_events_carry_principal_argv_pty_code now asserts that both events store the argv exactly as the request sent it.

Residuals

I cannot edit the PR body. Please add the Residuals section above to it.

@minimal-arch minimal-arch Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arch-review: PASS_WITH_CONDITIONS · 2 conditions · the full ruling

The code conforms to architecture.md line 497 for BOX-150 to BOX-153. The new commit keeps the exec argv as typed in the events, as line 497 says. Two pieces of work stay undone, and no issue owns them. First, mfile cannot parse [io] exec_enabled, so a real box spec cannot set it (C1). Second, the StopExec types are not in minimald-rpc, and no min box stop --exec command calls them (C2). File the two issues and list their URLs in the PR body. No code change is necessary.

Ruled at 95c66b3 against gominimal/arch@7a86ec7bb3c0 by claude-opus-5-5.

Comment thread crates/minimald/src/exec/process.rs Resolved
Comment thread crates/minimald/src/rpc.rs Resolved
@mitodrummer
Copy link
Copy Markdown
Member

/revise

Three fixes on 95c66b35, from the maintainer's pre-merge review. The maintainer accepts the out-of-layer edit to crates/minimald/tests/nsenter_integration.rs in item 3.

  1. Register a stop before the exec is visible. run_attached_exec writes exec_started (exec.rs ~550) before register_exec (~575) and register_stop (~578). An events follower that stops the exec right after exec_started gets "box X has no exec Y" (rpc.rs ~1511) for a running exec. The managed path registers the entry (exec/process.rs ~1281) before the stop token (~1282). A stop between those two lines takes the None arm (~1416), logs "already ended", and never fires. Register the stop token first, or keep it on ExecEntry, and write exec_started after both, on both paths. Add a test that stops an exec at the earliest point its id is observable and asserts that it ends.
  2. Answer a stop of an exec that did run, after a daemon restart. stop_exec (exec/process.rs ~1404) returns None when no live entry exists, so the handler says "no exec" for an id with a stored exec_exited. The rule on this task: "no such exec" only for an id this daemon never started in the box. Read stored_exec_exit the way serve_exec_wait does, and answer that the exec already ended, with its code. Add a test.
  3. Run the tty shim path end to end. The new setsid + TIOCSCTTY path (nsenter.rs ~845/~1095) and the session kill run only as host-side helper calls. If TIOCSCTTY failed, the pre_exec error would fail every tty exec's spawn. Add a tty-stdin case to crates/minimald/tests/nsenter_integration.rs (it runs the real shim, gated on MINIMALD_NSENTER_TEST). The case checks that the exec's PTY is its controlling terminal, and that a stop ends a backgrounded job in another process group of the session.

@minimal-foundry
Copy link
Copy Markdown
Contributor Author

That /revise is held, not dropped. The container platform had no instance to give the coder: its pool of prepared containers was full, which happens most just after a deploy. Your note is kept and asked again every 10 minutes for up to 30; a wait that outlives that is refused here with the reason.

What the platform said

could not start the coder: Error: there is no container instance that can be provided to this durable object

Nothing to type again: the command gets a rocket when its round starts, or a refusal that names the reason. No round was counted for it yet.

Register the stop token before the exec's entry, and write exec_started
after both, on the attached and the managed paths. A stop of a stored,
ended exec now answers that it already ended, with its code.
The case checks that the exec's PTY is its controlling terminal, and that
a stop ends a background job in another process group of its session.
@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minimal-review — advisory review

Verdict COMMENT — 2 findings, 2 inline.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

Comment thread crates/minimald/src/exec/process.rs Resolved
Comment thread crates/minimald/src/nsenter.rs Resolved
@minimal-arch minimal-arch Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arch-review: PASS_WITH_CONDITIONS · 2 conditions · the full ruling

The compare keeps BOX-151 conformant. A stop is now registered before the exec is visible, and a stop of an ended exec answers from the stored exec_exited event (line 497). Nothing else changes from the ruling on 95c66b35. C1 (#2973) and C2 (#2913) stay open as filed residuals, and the change can merge with them. The new shim test runs in no lane because nothing sets MINIMALD_NSENTER_TEST. Do not count it as a proof leg.

Ruled at b8edbaa against gominimal/arch@7a86ec7bb3c0 by claude-opus-5-5.

8
Comment thread crates/minimald/src/exec.rs Resolved
Comment thread crates/minimald/src/rpc.rs Resolved
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 4 of 10 on a /revise note, pushed as b8edbaac2c07.

line exit tail
cargo nextest run -p minimald pty_or_detached_exec_survives_client_loss 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-43f5... Compiling paths v0.0.1 (/workbench/crates/paths) Compiling args v0.0.1 (/workbench/crates/args) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling client-config v0.0.1 (/wo
cargo nextest run -p minimald stop_exec_ends_process_group 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-2ef5... Compiling stdlib v0.0.22 (/workbench/crates/stdlib) Compiling version v0.8.0 (/workbench/crates/version) Compiling decode v0.0.1 (/workbench/crates/decode) Compiling mlog v0.
cargo nextest run -p minimald exec_disabled_refuses_exit5 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-89f6... Compiling stdlib v0.0.22 (/workbench/crates/stdlib) Compiling version v0.8.0 (/workbench/crates/version) Compiling decode v0.0.1 (/workbench/crates/decode) Compiling mlog v0.
cargo nextest run -p minimald tty_exec_pty_is_controlling_terminal 0 Applying loadouts: default Running task runtime-implement-test-3 in session task-runtime-implement-test-3-3fa5... Compiling stdlib v0.0.22 (/workbench/crates/stdlib) Compiling version v0.8.0 (/workbench/crates/version) Compiling decode v0.0.1 (/workbench/crates/decode) Compiling mlog v0.
cargo nextest run -p minimald exec_events_carry_principal_argv_pty_code 0 Applying loadouts: default Running task runtime-implement-test-4 in session task-runtime-implement-test-4-052c... Compiling stdlib v0.0.22 (/workbench/crates/stdlib) Compiling version v0.8.0 (/workbench/crates/version) Compiling decode v0.0.1 (/workbench/crates/decode) Compiling mlog v0.

Verify min task run agent-verify passed.

Outside the task's layers::

  • crates/minimald/tests/nsenter_integration.rs

Converted to a draft: something above needs a person before review.

Review threads the coder judged:

  • 👎 invalid — PRRT_kwDOPdfcxM6zZjsB: Storing the argv as typed is deliberate. architecture.md line 497 says the argv is retained as typed, and the maintainer asked for that in an earlier round (the masked argv was removed). Event reads are gated by BoxRead, and min secret is the route for secrets. (not posted on the thread: not an open thread on the pull request)
  • 👎 invalid — crates/minimald/src/exec/process.rs:1058: The gap is real, but the fix is outside this task's layers: it needs an exec_enabled field on DocIo in crates/mfile/src/spec.rs. The coder has no credential to file the issue. A person must file it and link it in the PR body.
  • 👎 invalid — crates/minimald/src/rpc.rs:1446: Moving StopExec into crates/minimald-rpc and adding the min box stop --exec client are outside this task's layers. The coder has no credential to file the issue. A person must file it and link it in the PR body.

A terminal or detached exec keeps running after its client goes away. A stop of one exec ends it and the box keeps running. That is also true when the stop arrives as soon as the exec_started event is written. A stop of an exec that ended before a daemon restart answers that it already ended, with its code.

Notes from the agent: Item 1: both paths now register the stop token first, then the exec's entry, then write exec_started. A test-only hook in ExecBox::write_event (exec/process.rs, stop_at_start) stops the exec when its exec_started event is written. Tests use it for the attached, detached and tty modes. The attached test fails on the old order. The managed-path race was between register_exec and register_stop, and both came before the event, so the managed test pins the new order but does not reproduce the old race. Item 2: StopExecResponse gets already_ended (serde default). stop_exec_answer now takes the daemon state dir. It reads the stored exec_exited through BoxDir::find and stored_exec_exit when no live entry exists. A failed read of the box index or record answers that the end is not known, not 'no exec'. Item 3: tests/nsenter_integration.rs (outside the layers, accepted by the maintainer) gets tty_exec_pty_is_controlling_terminal_and_a_stop_ends_its_jobs. It is #[ignore] and gated on MINIMALD_NSENTER_TEST, like the other cases there. I ran it on this box with the gate set, and it passed. The two arch threads ask for issues to be filed and linked in the PR body. I have no credential to file issues, so a person must file them. just clippy-strict was not run.

@minimal-foundry
minimal-foundry Bot marked this pull request as draft November 4, 2026 02:37
@minimal-foundry minimal-foundry Bot added the needs-human An agent handed off; a human must act, then clear this label. Set by defer and handoffs. label Nov 4, 2026
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

The runtime will not revise this pull request again on its own: the diff is +1646/-59 (1705 lines), past 3x the plan's estimate of 480 lines (est: 220 code, 260 tests, as #2406 reads now), at round 4 (the last trigger was a /revise note). A person decides now. Remove needs-human from this pull request to let /revise <note> or the automatic rounds resume, or finish #2406 by hand.

@mitodrummer
mitodrummer marked this pull request as ready for review November 4, 2026 02:47
@mitodrummer mitodrummer left a comment
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at b8edbaac.

  • Earlier review: a whole-PR review at 95c66b35 found no blockers. It ran the ten tests and mutation-tested the attached-exec stop.
  • This round: it lands the three requested fixes.
    • The stop is registered before the exec is visible, on the attached and managed paths alike (8972c6af), with first-sight stop tests.
    • A stop after a daemon restart answers from the stored exec_exited (stored_exec_stop) and keeps "no such exec" for ids never started.
    • A tty exec runs through the real nsenter shim in nsenter_integration.rs (b8edbaac).
  • Design: argv is stored as typed in events, per architecture.md line 497 and arch#76, and log lines stay scrubbed.
  • Residuals: #2973 (the mfile exec_enabled field) and #2913 (the RPC move and the min box stop --exec client). Follow-ups are on #2978.
  • The macOS unit failure was an unrelated minvmd proxy-reclaim flake, and it passed on rerun.

@mitodrummer mitodrummer removed the needs-human An agent handed off; a human must act, then clear this label. Set by defer and handoffs. label Nov 4, 2026
@mitodrummer
mitodrummer added this pull request to the merge queue Nov 4, 2026
Merged via the queue into main with commit 8a65e47 Nov 4, 2026
40 of 42 checks passed
@mitodrummer
mitodrummer deleted the agent/2406-keep-terminal-and-detached-execs-past-a-client-l branch November 4, 2026 04: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.

T31 — Keep terminal and detached execs past a client loss, stop one exec, and honour exec_enabled

1 participant

0