8000 feat(minimald): T44 — Detach from an exec attach with the detach keys by minimal-foundry[bot] · Pull Request #3341 · gominimal/minimal · GitHub
[go: up one dir, main page]

Skip to content

feat(minimald): T44 — Detach from an exec attach with the detach keys - #3341

Draft
minimal-foundry[bot] wants to merge 7 commits into
mainfrom
agent/3331-detach-from-an-exec-attach-with-the-detach-keys
Draft

minimal-foundry[bot] wants to merge 7 commits into
mainfrom
agent/3331-detach-from-an-exec-attach-with-the-detach-keys

Conversation

@minimal-foundry
Copy link
Copy Markdown
Contributor

T44 — Detach from an exec attach with the detach keys

slice: S4
kind: task
covers: BCLI-068
size: M
est: 140 code, 160 tests
executor: agent
difficulty: needs care
model: strongest
diagnostics: a detach from an exec attach exits 0, pinned by exec_attach_detach_key_returns_0_exec_keeps_running
observability: none new
layers:
- client:
- crates/minimal/src/cmd/boxes/attach.rs: the exec attach and min box exec -t's first attach send the session keys, as the box attach does
- crates/minimal-client/src/attach.rs: attach_command sends the session keys on an ExecAttach channel
- crates/minimal/src/tests.rs: the in-process minimald test harness gains this layer's tests
test: cargo nextest run -p minimal exec_attach_detach_key_returns_0_exec_keeps_running
- daemon:
- crates/minimald/src/exec/bridge.rs: serve_exec_attach runs the same leader/detach state machine as the box PTY attach. A detach ends the channel as a detach, with exit 0, and leaves the exec running.
test: cargo nextest run -p minimald serve_exec_attach_detach_key_ends_channel_exec_runs
produces: none
consumes: none
proof: Ctrl-] then d ends min box attach <box> --exec <id> with exit 0, the exec keeps running, and a second attach reaches it.

Proof

Ctrl-] then d (or the configured leader and detach keys) ends min box attach <box> --exec <id> with exit 0. The terminal shows Detached from exec <id>. It keeps running., the exec keeps running, min box show still lists it, and a second min box attach <box> --exec <id> reaches it.

Notes from the agent: Out of scope: crates/minimald/src/exec.rs changed in one call site. serve_exec_attach needs the keys the channel negotiated, and only the dispatcher in exec.rs holds the channel's env vars. Without this change the daemon cannot honour a remapped leader or detach key.

Not done: min box exec -t's first attach is in crates/minimal/src/cmd/boxes/exec.rs (attach_exec_terminal), not in attach.rs, and that path has no config_dir. It still sends no keys. The daemon now gives a channel without keys the default keys, so Ctrl-] d detaches there too, but a remapped chord in config.toml does not apply to min box exec -t. A follow-up can thread global.config_dir into TtyAttach::Terminal and call exec_attach_ssh.

minimal-client/src/attach.rs: attach_command alrea 8000 dy sent the keys when given Some. Only its doc comments changed, to say an ExecAttach channel sends them.

New printed text, not fixed by the task: the daemon writes Detached from exec <id>. It keeps running. on the terminal at a detach. The box attach writes Detaching from session. in the same place. The exec detach does not write terminal unwind codes, because the daemon does not track the exec terminal's modes.

The minimal test cannot drive a real detach through the in-process harness, because TestServer::list_running_exec registers an exec with no terminal. It checks the keys the attach's ssh sends (default and configured), the 0 ending, the live listing and a second attach. The minimald test drives the detach through attach_terminal_keyed on a live terminal.

Tests

line exit tail
cargo nextest run -p minimal exec_attach_detach_key_returns_0_exec_keeps_running 0 warning: unknown fields in task runtime-implement-test-0: network warning: unknown fields in task runtime-implement-test-1: network help: do you need to update to a newer version of minimal? Applying loadouts: default Running task runtime-implement-test-0 in box runtime-implement-test-0-1... Comp
cargo nextest run -p minimald serve_exec_attach_detach_key_ends_channel_exec_runs 0 warning: unknown fields in task runtime-implement-test-0: network warning: unknown fields in task runtime-implement-test-1: network help: do you need to update to a newer version of minimal? Applying loadouts: default Running task runtime-implement-test-1 in box runtime-implement-test-1-1... Comp

Verify

min task run agent-verify timed out after 1800s, so this PR is a draft.

Checking switch v0.0.1 (/workbench/crates/switch)
    Checking mlog v0.0.1 (/workbench/crates/mlog)
    Checking common v0.0.1 (/workbench/crates/common)
    Checking diagnostics v0.0.1 (/workbench/crates/diagnostics)
    Checking async-dialog v0.0.1 (/workbench/crates/async-dialog)
   Compiling minvmd v0.8.0 (/workbench/crates/minvmd)
    Checking boxes v0.0.1 (/workbench/crates/boxes)
    Checking sandbox v0.0.1 (/workbench/crates/sandbox)
    Checking mfile v0.0.1 (/workbench/crates/mfile)
    Checking minimald-rpc v0.0.1 (/workbench/crates/minimald-rpc)
    Checking decode v0.0.1 (/workbench/crates/decode)
    Checking checkouts v0.0.1 (/workbench/crates/checkouts)
    Checking graph v0.0.1 (/workbench/crates/graph)
    Checking minimal-client v0.8.0 (/workbench/crates/minimal-client)
    Checking lcache v0.0.1 (/workbench/crates/lcache)
    Checking rcache v0.0.1 (/workbench/crates/rcache)
    Checking op v0.0.1 (/workbench/crates/op)
    Checking check v0.0.1 (/workbench/crates/check)
    Checking orchestrator v0.0.1 (/workbench/crates/orchestrator)
    Checking mctx v0.0.1 (/workbench/crates/mctx)
    Checking minimald v0.8.0 (/workbench/crates/minimald)
    Checking minimal v0.8.0 (/workbench/crates/minimal)
    Finished `dev` profile [unoptimized] target(s) in 3m 50s
   Compiling opentelemetry v0.33.1
   Compiling version v0.8.0 (/workbench/crates/version)
   Compiling stdlib v0.0.22 (/workbench/crates/stdlib)
   Compiling serial_test v4.0.1
   Compiling opentelemetry_sdk v0.33.1
   Compiling tracing-opentelemetry v0.34.0
   Compiling opentelemetry-http v0.33.1
   Compiling opentelemetry-appender-tracing v0.33.1
   Compiling google-cloud-gax-internal v0.7.20
   Compiling opentelemetry-proto v0.33.0
   Compiling opentelemetry-otlp v0.33.0
   Compiling google-cloud-longrunning v1.14.0
   Compiling google-cloud-iam-v1 v1.13.0
   Compiling mlog v0.0.1 (/workbench/crates/mlog)
   Compiling google-cloud-lro v1.11.0
   Compiling google-cloud-storage v1.20.0
   Compiling common v0.0.1 (/workbench/crates/common)
   Compiling boxes v0.0.1 (/workbench/crates/boxes)
   Compiling sandbox v0.0.1 (/workbench/crates/sandbox)
   Compiling mfile v0.0.1 (/workbench/crates/mfile)
   Compiling minimald-rpc v0.0.1 (/workbench/crates/minimald-rpc)
   Compiling decode v0.0.1 (/workbench/crates/decode)
   Compiling checkouts v0.0.1 (/workbench/crates/checkouts)
   Compiling minimal-client v0.8.0 (/workbench/crates/minimal-client)
   Compiling minvmd v0.8.0 (/workbench/crates/minvmd)
   Compiling graph v0.0.1 (/workbench/crates/graph)
   Compiling lcache v0.0.1 (/workbench/crates/lcache)
   Compiling rcache v0.0.1 (/workbench/crates/rcache)
   Compiling op v0.0.1 (/workbench/crates/op)
   Compiling check v0.0.1 (/workbench/crates/check)
   Compiling orchestrator v0.0.1 (/workbench/crates/orchestrator)
   Compiling mctx v0.0.1 (/workbench/crates/mctx)
   Compiling minimald v0.8.0 (/workbench/crates/minimald)
   Compiling minimal v0.8.0 (/workbench/crates/minimal)

Out of scope

These files are outside the task's layers:, so this PR is a draft:

  • crates/minimald/src/exec.rs

Closes #3331

serve_exec_attach now runs the leader and detach state machine of the
box's own terminal, with the keys the channel negotiated. A detach ends
the channel with status 0 and leaves the exec running (BCLI-068).
min box attach <box> --exec <id> now sends the session keys from the
config, as the box attach does, so the daemon detaches on them (BCLI-068).
@coderabbitai
coderabbitai Bot commented Nov 12, 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: c45a7301-d07e-49ce-bed5-a5f8c0f88fde

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 12, 2026 •
Copy link
Copy Markdown
Contributor Author

Reviewed: no findings; approval withheld, 2 threads unresolved at 9a4287f2247c.

APPROVE withheld: 2 unresolved threads — minimal-arch[bot] crates/minimald/src/exec.rs:501, minimal-foundry[bot] crates/minimald/src/exec/bridge.rs:2290. Resolving the last thread releases the approval on this head.

Resolve the open threads. Resolving the last one releases the approval on this head; no new review is required.

Clobber: none seen.

@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 · 1 condition · the full ruling

The min box attach --exec path conforms. The daemon detaches with status 0 and keeps the exec running, as architecture.md lines 492 and 498 say. The first attach of min box exec -t still sends no keys. The daemon now gives it the default chord, so a remapped leader or detach key does not apply there. Line 498 says a -t exec re-attaches like a PTY box, and the PTY box uses the configured keys. The PR body names this gap but links no issue. Make the edit in C1, or file the issue and name it as a residual.

Ruled at b1370c2 against gominimal/arch@15b603e6afb8 by claude-opus-5-5.

spawn(serve_exec_attach(channel, session_handle, session_id, exec_id).instrument(span));
// The keys the client negotiated, as a box attach reads them
// (BCLI-068). A client that sent none gets the default keys.
let keys = boxes::keys::SessionKeys::from_env(&config.env_vars).validated_or_default();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

C1 · Here a channel without keys gets the default chord. The first attach of min box exec -t sends no keys (attach_exec_terminal in crates/minimal/src/cmd/boxes/exec.rs). So a remapped leader or detach key does not apply there. Send the configured keys from that client path, as C1 says.

10000

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.

Addressed in 9a4287f2247c: attach_exec_terminal in min box exec -t now builds its ssh through exec_attach_ssh, so it sends the session keys from --config-dir like min box attach --exec.

@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 on lines +2265 to +2268
if detach {
out.write_all(detached_notice(&entry.id).as_bytes()).await?;
out.flush().await?;
return Ok(AttachEnd::Detached);
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.

MEDIUM · correctness · confidence 0.60 · in relay_terminal_keyed

The detach path awaits out.write_all and out.flush for the notice before it returns Detached. If the client stopped reading, the SSH window is full and these awaits never finish, so the detach chord does nothing. The box terminal in box_runtime.rs (KeyAction::Detach) bounds this with send_timeout and sheds the binding, because the detach chord is how a user leaves a stalled attach. Bound the notice write with a timeout, or return Detached first and ignore a write error. The bell write in EnterCommandMode has the same unbounded await.

What the reviewer checked

out.write_all(detached_notice(&entry.id).as_bytes()).await?; out.flush().await?; return Ok(AttachEnd::Detached);

Hand this to an agent
Act on a code-review finding.

File:       crates/minimald/src/exec/bridge.rs
Lines:      2265-2268
Category:   correctness
Severity:   medium (reviewer confidence 0.60)

Finding:
The detach path awaits `out.write_all` and `out.flush` for the notice before it returns `Detached`. If the client stopped reading, the SSH window is full and these awaits never finish, so the detach chord does nothing. The box terminal in `box_runtime.rs` (KeyAction::Detach) bounds this with `send_timeout` and sheds the binding, because the detach chord is how a user leaves a stalled attach. Bound the notice write with a timeout, or return `Detached` first and ignore a write error. The bell write in `EnterCommandMode` has the same unbounded await.

First verify this against the code yourself. It is model output and
may be wrong. If it is wrong, or the tradeoff was deliberate, say so
and change nothing.

👍 useful · 👎 noise — react to tell the reviewer. Replying works too, and says more.

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.

Addressed in 9a4287f2247c: The bell and the detach notice now go through write_bounded (EXIT_NOTICE_BOUND, errors ignored). A new test pins a detach with status 0 for a client that does not read.

A client that does not read held the detach on the notice write. The
writes now wait at most EXIT_NOTICE_BOUND, and the detach goes on.
The first terminal attach of min box exec -t sent no keys, so a
remapped leader or detach key did not apply. It now builds its ssh
command through exec_attach_ssh, as min box attach --exec does.
@agent-137
Copy link
Copy Markdown
Member

/revise

Head b1370c24. Arch ruling 1215 C1, and one minimal-review finding. Change nothing else.

1. C1: min box exec -t's first attach sends the configured keys.
attach_exec_terminal() (crates/minimal/src/cmd/boxes/exec.rs) takes config_dir: Option<&Path> and builds its ssh through crate::cmd::boxes::attach::exec_attach_ssh(sock, id, exec_id, config_dir), or attach_command(..., Some(&resolve_session_keys(config_dir)?)), instead of attach_command(sock, id, Some(attach_wire), None). cmd_box_exec passes global.config_dir.as_deref() through TtyAttach::Terminal. Add a tests.rs case: with detach = "q" configured, that ssh carries the leader and detach env vars and the SendEnv options. exec.rs is outside layers:, but the change needs it.

2. The detach can't stall behind a full window (thread PRRT_kwDOPdfcxM623fwj).
In relay_terminal_keyed, the detach path awaits out.write_all and out.flush for the notice before it returns Detached. If the client stopped reading, the SSH window is full, those awaits never finish, and the chord does nothing. Bound the notice write with a timeout, as the box terminal in box_runtime.rs does for KeyAction::Detach with send_timeout, or return Detached first and ignore a write error. Treat the bell write in EnterCommandMode the same way.

Reply on both threads with the commit. Run the whole minimal and minimald suites.

@minimal-foundry
Copy link
Copy Markdown
Contributor Author

That /revise is held, not dropped. A coder is already running on #3331. Your note is kept and goes into the next round here, which starts when that coder finishes.

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.

@minimal-foundry
Copy link
Copy Markdown
Contributor Author

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

line exit tail
cargo nextest run -p minimal exec_attach_detach_key_returns_0_exec_keeps_running 0 warning: unknown fields in task runtime-implement-test-0: network warning: unknown fields in task runtime-implement-test-1: network help: do you need to update to a newer version of minimal? Applying loadouts: default Running task runtime-implement-test-0 in box runtime-implement-test-0-1... Comp
cargo nextest run -p minimald serve_exec_attach_detach_key_ends_channel_exec_runs 0 warning: unknown fields in task runtime-implement-test-0: network warning: unknown fields in task runtime-implement-test-1: network help: do you need to update to a newer version of minimal? Applying loadouts: default Running task runtime-implement-test-1 in box runtime-implement-test-1-1... Comp

Verify min task run agent-verify timed out after 1800s.

Checking common v0.0.1 (/workbench/crates/common)
    Checking switch v0.0.1 (/workbench/crates/switch)
    Checking diagnostics v0.0.1 (/workbench/crates/diagnostics)
    Checking async-dialog v0.0.1 (/workbench/crates/async-dialog)
   Compiling minvmd v0.8.0 (/workbench/crates/minvmd)
    Checking mlog v0.0.1 (/workbench/crates/mlog)
    Checking boxes v0.0.1 (/workbench/crates/boxes)
    Checking sandbox v0.0.1 (/workbench/crates/sandbox)
    Checking mfile v0.0.1 (/workbench/crates/mfile)
    Checking minimald-rpc v0.0.1 (/workbench/crates/minimald-rpc)
    Checking decode v0.0.1 (/workbench/crates/decode)
    Checking checkouts v0.0.1 (/workbench/crates/checkouts)
    Checking graph v0.0.1 (/workbench/crates/graph)
    Checking minimal-client v0.8.0 (/workbench/crates/minimal-client)
    Checking lcache v0.0.1 (/workbench/crates/lcache)
    Checking rcache v0.0.1 (/workbench/crates/rcache)
    Checking op v0.0.1 (/workbench/crates/op)
    Checking check v0.0.1 (/workbench/crates/check)
    Checking orchestrator v0.0.1 (/workbench/crates/orchestrator)
    Checking mctx v0.0.1 (/workbench/crates/mctx)
    Checking minimald v0.8.0 (/workbench/crates/minimald)
    Checking minimal v0.8.0 (/workbench/crates/minimal)
    Finished `dev` profile [unoptimized] target(s) in 1m 12s
   Compiling opentelemetry v0.33.1
   Compiling version v0.8.0 (/workbench/crates/version)
   Compiling stdlib v0.0.22 (/workbench/crates/stdlib)
   Compiling serial_test v4.0.1
   Compiling opentelemetry_sdk v0.33.1
   Compiling tracing-opentelemetry v0.34.0
   Compiling opentelemetry-http v0.33.1
   Compiling opentelemetry-appender-tracing v0.33.1
   Compiling google-cloud-gax-internal v0.7.20
   Compiling opentelemetry-proto v0.33.0
   Compiling opentelemetry-otlp v0.33.0
   Compiling google-cloud-longrunning v1.14.0
   Compiling google-cloud-iam-v1 v1.13.0
   Compiling mlog v0.0.1 (/workbench/crates/mlog)
   Compiling google-cloud-lro v1.11.0
   Compiling google-cloud-storage v1.20.0
   Compiling common v0.0.1 (/workbench/crates/common)
   Compiling boxes v0.0.1 (/workbench/crates/boxes)
   Compiling sandbox v0.0.1 (/workbench/crates/sandbox)
   Compiling mfile v0.0.1 (/workbench/crates/mfile)
   Compiling minimald-rpc v0.0.1 (/workbench/crates/minimald-rpc)
   Compiling decode v0.0.1 (/workbench/crates/decode)
   Compiling checkouts v0.0.1 (/workbench/crates/checkouts)
   Compiling minimal-client v0.8.0 (/workbench/crates/minimal-client)
   Compiling minvmd v0.8.0 (/workbench/crates/minvmd)
   Compiling graph v0.0.1 (/workbench/crates/graph)
   Compiling lcache v0.0.1 (/workbench/crates/lcache)
   Compiling rcache v0.0.1 (/workbench/crates/rcache)
   Compiling op v0.0.1 (/workbench/crates/op)
   Compiling check v0.0.1 (/workbench/crates/check)
   Compiling orchestrator v0.0.1 (/workbench/crates/orchestrator)
   Compiling mctx v0.0.1 (/workbench/crates/mctx)
   Compiling minimald v0.8.0 (/workbench/crates/minimald)
   Compiling minimal v0.8.0 (/workbench/crates/minimal)

Outside the task's layers::

  • crates/minimal/src/cmd/boxes/exec.rs
  • crates/minimald/src/exec.rs

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

Review threads the coder judged:

  • 👍 valid — crates/minimald/src/exec.rs:501: attach_exec_terminal in min box exec -t now builds its ssh through exec_attach_ssh, so it sends the session keys from --config-dir like min box attach --exec.
  • 👍 valid — crates/minimald/src/exec/bridge.rs:2290: The bell and the detach notice now go through write_bounded (EXIT_NOTICE_BOUND, errors ignored). A new test pins a detach with status 0 for a client that does not read.

Ctrl-] then d ends min box attach <box> --exec <id> with exit 0, the exec keeps running, and a second attach reaches it. The detach also works when the client does not read, and min box exec -t now sends the user's configured keys, so a remapped detach key works on its first attach too.

Notes from the agent: This round touched crates/minimal/src/cmd/boxes/exec.rs, which is outside the layers. The task's attach.rs note asks that min box exec -t's first attach send the session keys, and that attach is built in exec.rs (attach_exec_terminal). It now calls exec_attach_ssh from attach.rs, and box_exec_on_hosts takes a config_dir argument. The unused with_forced_tty helper is removed. box_exec_on_hosts now has 8 arguments, so it carries #[allow(clippy::too_many_arguments)], as five other functions in the crate do. The daemon fix bounds only the bell and the detach notice. A live output write to a client that does not read can still stall the relay loop before it reads the detach keys. That is a wider issue than this finding, and it is not changed here. just clippy-strict was not run.

@minimal-foundry
minimal-foundry Bot marked this pull request as draft November 12, 2026 23:40
@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 · the full ruling

The change conforms to the design. A -t exec now detaches with the detach keys, ends the attach with 0, and runs on, as architecture.md line 498 and line 492 require. The two commits after the earlier head close its gaps. The detach notice cannot hold the detach, and min box exec -t sends the configured keys. The PR body still says min box exec -t sends no keys. Update that paragraph before the PR leaves draft.

Ruled at 9a4287f against gominimal/arch@15b603e6afb8 by claude-opus-5-5.

@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.

APPROVE withheld: 2 unresolved threads — minimal-arch[bot] crates/minimald/src/exec.rs:501, minimal-foundry[bot] crates/minimald/src/exec/bridge.rs:2290.

minimal-review — advisory review

Verdict COMMENT — No findings.

Panel: 2 of 3 personas ran (correctness, security); did not complete: supply-chain.

This branch has not been deployed

No deployments
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.

T44 — Detach from an exec attach with the detach keys

1 participant

0