Skip to content

fix: close the launcher's descriptors in the sandbox intermediate - #23

Merged
sstriker merged 2 commits into
mainfrom
fix/confined-capture-hang
Oct 25, 2026
Merged

sstriker merged 2 commits into
mainfrom
fix/confined-capture-hang

Conversation

@sstriker

Copy link
Copy Markdown
Contributor

A confined launch with a prompt (amem launch --prompt) stopped forever when the harness wrote more than 64 KiB to stdout. The harness container survey in #2 found this.

The cause

A confined launch forks twice. The second fork (become_pid_one) makes pid 1 of the new pid namespace. Its parent, the intermediate, never runs exec. It only waits for pid 1 and exits.

std::process::Command::spawn reads its exec-error channel until end of file. With this toolchain, that channel is a SOCK_SEQPACKET socket pair. The intermediate held a copy of every descriptor of the launcher, that channel included. So spawn() returned only at the end of the run. Until then, nothing read the capture pipe. When the pipe was full, the harness stopped in its write.

This was seen on a hung launch: the launcher was in recv on the socket pair, and the intermediate held the other end.

The change

crates/sandbox/src/linux.rs: the intermediate closes every descriptor from 3 up, right after the fork (close_from_three). It uses close_range, or a close loop below the open-files limit on an older kernel. Both are system calls only, with no allocation.

  • Pid 1 keeps its own copy of the channel, so an exec error is still reported. A setup failure inside pid 1 still reaches spawn as an error.
  • The intermediate keeps descriptors 0 to 2. It writes to 2 when the relay dies. It exits right after pid 1, so the read of stdout still gets end of file.

Test

crates/memory/tests/launch.rs: a_confined_prompted_run_captures_more_than_a_pipe_holds. A confined amem launch --prompt hello --no-handoff runs a harness that prints begin, 200000 bytes of a, and end. The test asserts exit 0 and the exact output. After 90 s it stops the launch and fails, so a regression does not hang CI.

Failing first

Against main's linux.rs: the launch had not returned after 90 s, with 0 bytes out.

With the fix, the launch tests pass in 2.84 s. make check after the merge of main: 656 tests, 656 passed, 6 skipped.

Adversarial pass

A fresh agent attacked this branch and the stacked branch fix/killed-launch-cleanup. With CI=1, the confined tests ran. For this branch it found these points:

  • The intermediate needs no descriptor above 2. It only writes to 2, waits and exits.
  • With close_from_three removed, the new test fails at its 90 s bound.
  • The test has no cleanup guard. When it fails, it leaves a dead FUSE mount and a scratch directory under its temporary directory. The stacked branch adds that guard.

The other findings are for the stacked branch. They are fixed there, before its own pull request.

Posted by Claude on behalf of @sstriker

sstriker and others added 2 commits October 25, 2026 01:27
A confined run on Linux stopped when the harness wrote more than
64 KiB to standard output. The process that becomes pid 1 forks from
an intermediate process. The intermediate does not exec, so it kept
open all the descriptors of the launcher. One of these is the write
end of the exec-error socket that std reads in spawn. Thus spawn did
not return until the run ended, nothing read the capture pipe, and
the harness stopped when the pipe was full.

Now the intermediate closes all descriptors from 3 up after its fork
(close_range, or a close loop on a kernel without it). Pid 1 keeps its
own copy of the exec-error socket, thus a failure before its exec
still gets to spawn.

Add a launch test: a confined prompted run whose harness writes
200000 bytes completes in time, and all of the output gets to the
launcher.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sstriker
sstriker requested review from andrewmusselman and a balanced review from Copilot October 25, 2026 00:20

Copilot AI 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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI 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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI 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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI 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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI 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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI 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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI 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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@sstriker
sstriker merged commit 41db64a into main Oct 25, 2026
11 of 18 checks passed
@sstriker
sstriker deleted the fix/confined-capture-hang branch October 25, 2026 07:38
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.

2 participants