8000 T24 — Copy files out of a box and read its events by minimal-foundry[bot] · Pull Request #3016 · gominimal/minimal · GitHub
[go: up one dir, main page]

Skip to content

T24 — Copy files out of a box and read its events - #3016

Merged
agent-137 merged 12 commits into
mainfrom
agent/2908-copy-files-out-of-a-box-and-read-its-events
Nov 5, 2026
Merged

agent-137 merged 12 commits into
mainfrom
agent/2908-copy-files-out-of-a-box-and-read-its-events

Conversation

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

T24 — Copy files out of a box and read its events

slice: S6
kind: task
covers: BCLI-062, BCLI-016
size: L
est: 320 code, 280 tests
executor: agent
difficulty: routine
model: standard
diagnostics: min box events <box> -o jsonl prints one event per line with schema, ts, box, parent, type and data, pinned by events_jsonl_replay_schema_fields
observability: the command's existing tracing span records the verb, the box id it resolved and the outcome
layers:
- library:
- crates/minimal-client/src/lib.rs: a public Client method opens a session subsystem channel for a box (MINIMAL_SESSION_ID set) and streams its reply line by line, for min box events (READ_EVENTS_SUBSYSTEM, with follow) and the sftp read of a retained box for min box cp
test: cargo nextest run -p minimal-client session_subsystem_channel_streams_reply_lines
- client:
- crates/minimal/src/cli.rs: the verb's arguments and help in the one command model
- crates/minimal/src/cmd/mod.rs: dispatches the verb
- crates/minimal/src/cmd/boxes/events.rs (new): min box events [--follow] [--parent] and min box cp from a retained filesystem
- crates/minimal/src/cmd/boxes.rs: declares the events module
- crates/minimal/src/tests.rs: the in-process minimald test harness gains this layer's tests
test: cargo nextest run -p minimal box_cp_reads_stopped_box_as_box_017
test: cargo nextest run -p minimal events_jsonl_replay_schema_fields
test: cargo nextest run -p minimal events_follow_replays_then_tails
test: cargo nextest run -p minimal events_parent_merges_children
produces:
- crates/minimal/src/cmd/boxes/events.rs (entry point)
consumes:
- crates/minimal/src/cmd/boxes/address.rs
proof: min box cp copies a file out of a stopped box, and min box events replays and follows its events.
depends on:
- blocked by #2895

Proof

A person can run min box cp web:notes.txt . against a stopped box and get notes.txt from the files the box kept. min box events web -o jsonl prints one min/v1/event line for each event (schema, ts, box, parent, type, data), oldest first. --follow prints the kept events, then each new event, and ends when the box is removed. --parent <box> merges the events of every child box in time order.

Notes from the agent: Outside the layers: crates/minimal-client/Cargo.toml gains russh-sftp.workspace = true, and Cargo.lock gains that one edge. The sftp read of a retained box needs an SFTP client, and russh-sftp is already a workspace dependency (minimald, minvmd). The alternative was a hand-written SFTP client in minimal-client.

Not done, because the file is outside the layers: main.rs machine_output_mode does not list min box events -o jsonl. So a failure under -o jsonl prints a plain line, not a min/v1/error object. A one-arm change in crates/minimal/src/main.rs adds it, as for min box audit.

Behaviour to know: under --parent --follow the events print in arrival order across the children, not sorted by time. Without --follow they are sorted by ts. The set of children is read once at the start; following children created later is residual #3024. min box cp copies one file out of a box. It does not copy directories or copy into a box.

The daemon never sets a box's parent today. The --parent test therefore writes parent into the children's record.json on disk, as the minimald box_manager tests do.

One finding outside this task: Client::exec_channel sends MINIMAL_SESSION_ID with want_reply=true and then reads the first Success as the exec ack. That Success is the env ack, so a refused exec can look accepted. The new open_box_subsystem sends the env without a reply for this reason.

Tests

line exit tail
cargo nextest run -p minimal-client session_subsystem_channel_streams_reply_lines 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-f862... Compiling args v0.0.1 (/workbench/crates/args) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling paths v0.0.1 (/workbench/crates/paths) Compiling client-config v0.0.1 (/wo
cargo nextest run -p minimal box_cp_reads_stopped_box_as_box_017 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-8b3b... 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 minimal events_jsonl_replay_schema_fields 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-6100... 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 minimal events_follow_replays_then_tails 0 Applying loadouts: default Running task runtime-implement-test-3 in session task-runtime-implement-test-3-f4dc... 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 minimal events_parent_merges_children 0 Applying loadouts: default Running task runtime-implement-test-4 in session task-runtime-implement-test-4-7c21... 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 exited 100 under rustc 1.99.0, which the repository does not pin (1.99.1 in rust-toolchain.toml); the PR is not a draft on that alone — CI, which honours the pin, is the gate.

",
                "span_id": "8312244b7423d974",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "GetPaths",
                "parent_span_id": "b790184c2086e40f",
                "span_id": "8649772125efaba2",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "GetRecord",
                "parent_span_id": "b790184c2086e40f",
                "queued_behind": "0",
                "span_id": "22506568e164e273",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "DeleteSession",
                "parent_span_id": "b790184c2086e40f",
                "queued_behind": "0",
                "span_id": "69f5cc529fcfcb74",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "Destroy",
                "parent_span_id": "69f5cc529fcfcb74",
                "span_id": "8e05d0fdb7401ab3",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "HandOverName",
                "parent_span_id": "69f5cc529fcfcb74",
                "queued_behind": "0",
                "span_id": "3d20c5c371100906",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
    ]
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

  Cancelling due to test failure: 3 tests still running
        PASS [   5.014s] (984/995) minimal-client box_registration::tests::resume_timeout_goes_on_and_an_absent_host_fails_closed
        PASS [   4.512s] (985/995) minimal-client tty_relay::tests::resume_after_drop_prints_line_and_nudges_winsize
        PASS [   6.258s] (986/995) minimal-client box_registration::tests::a_silent_daemon_does_not_hold_the_exit_past_the_deadline
────────────
     Summary [  54.689s] 986/995 tests run: 985 passed, 1 failed, 1 skipped
        FAIL [   3.234s] (983/995) minimal::otel_cli exec_run_attach_and_task_run_share_the_commands_trace
warning: 9/995 tests were not run due to test failure (run with --no-fail-fast to run all tests, or run with --max-fail)
error: test run failed

Base

The base branch moved while the coder worked and was merged in before the tests ran. These files changed on BOTH sides since the branch point — read them first, because a rewrite from a stale reading can undo a merge without a conflict:

  • crates/minimal/src/tests.rs

Out of scope

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

  • crates/minimal-client/Cargo.toml

Closes #2908

Client::open_box_subsystem opens a channel for one box with
MINIMAL_SESSION_ID set. stream_box_subsystem reads its reply one line
at a time, for the ReadEvents read. read_box_file reads one file over
the box's sftp subsystem, which serves a stopped box's retained files.
min box events replays a box's events as the daemon reads them, with
-o jsonl for one min/v1/event object a line. --follow replays and then
tails until the box is removed. --parent merges the streams of every
child of a box in time order.

min box cp <box>:<path> <local> copies one file out of a box over its
sftp subsystem. A stopped or exited box is served from its kept files.
…-out-of-a-box-and-read-its-events

# Conflicts:
#	crates/minimal/src/tests.rs
@coderabbitai
coderabbitai Bot commented Nov 4, 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: cd77a654-aa74-471e-8046-8fef2c8c05b2

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

@agent-137
Copy link
Copy Markdown
Member

/revise Review at head 7d7bcf3. The design is sound: the follow lifecycle, the bounded mpsc, the env replies, path safety and the JSON schema are all correct. Fix these:

  1. crates/minimal-client/src/lib.rs ~1280-1285: SubsystemLines sits between append_daemon_error's doc comment and its fn. The public type's rustdoc now opens "Appends data to the daemon's error message…", and append_daemon_error has no doc. Move the struct above that doc block.
  2. User-facing errors (AGENTS.md, User-facing text). open_box_subsystem prints "the daemon refused the {subsystem} subsystem for box {box_id}". The contexts "open channel for the {subsystem} subsystem", "request the {subsystem} subsystem" and "start the sftp conversation" can reach the user too. For example, when the box is removed between address resolution and the cp, the user sees "Could not copy notes.txt out of box web: the daemon refused the sftp subsystem for box ". Rewrite these in the user's words: what could not be read from which box (by the address the user typed), and one next step such as min box show web. Keep internal detail (subsystem, channel, uuid) in the log. Also stop the path printing twice: events.rs ~254 adds "Could not copy {path} out of box {address}" on top of the library's "read {path}". The same rules apply to the events stream's errors.
  3. events.rs ~242: "Run min box cp {address}:<path>/<file> <local>." <local> is the known destination. Fill it in, as the message at ~232-234 does.
  4. min box cp holds the whole file in memory (read_box_file → sftp.read(path) into a Vec, then a blocking std::fs::write in async code). Stream it: read in chunks into a temp file next to the destination, then rename it into place, so a large file is not held in RAM and a failed copy leaves no partial file. Keep the test that the file is not created when the read fails.
  5. New public API says boxes::SessionId. AGENTS.md wants new code to say BoxId (the alias exists). Use BoxId in the new signatures.
  6. events.rs ~75: "Name a box: run min box events <box>." cannot be reached (the clap ArgGroup already requires a box or --parent). Remove it, or make it name --parent too if it is reachable.

Cargo.toml (russh-sftp, already a workspace dependency) and the matching Cargo.lock line are accepted as wiring. Change nothing else.

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

min box cp streams the file into a temporary file beside the
destination and renames it into place, so a large file is not held in
memory and a failed copy leaves no file. The library opens the file as
a stream and says why an open failed. The errors of min box cp and
min box events name the box by the address the person typed and give
one next step. The internal detail goes to the log. New signatures say
BoxId, and the doc of append_daemon_error is back on its fn.
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 1 of 10 on a /revise note, pushed as d9242f893cd9.

line exit tail
cargo nextest run -p minimal-client session_subsystem_channel_streams_reply_lines 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-08ec... Compiling args v0.0.1 (/workbench/crates/args) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling paths v0.0.1 (/workbench/crates/paths) Compiling client-config v0.0.1 (/wo
cargo nextest run -p minimal box_cp_reads_stopped_box_as_box_017 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-445f... Compiling paths v0.0.1 (/workbench/crates/paths) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling args v0.0.1 (/workbench/crates/args) Compiling client-config v0.0.1 (/wo
cargo nextest run -p minimal events_jsonl_replay_schema_fields 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-1ae1... 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 minimal events_follow_replays_then_tails 0 Applying loadouts: default Running task runtime-implement-test-3 in session task-runtime-implement-test-3-9633... 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 minimal events_parent_merges_children 0 Applying loadouts: default Running task runtime-implement-test-4 in session task-runtime-implement-test-4-0e82... 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 exited 100 under rustc 1.99.0, which the repository does not pin (1.99.1 in rust-toolchain.toml); the PR is not a draft on that alone — CI, which honours the pin, is the gate.

",
                "span_id": "ff4c694166211804",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "GetPaths",
                "parent_span_id": "7c75cf4e166c6f31",
                "span_id": "1b4acdf0dca349cf",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "GetRecord",
                "parent_span_id": "7c75cf4e166c6f31",
                "queued_behind": "0",
                "span_id": "375e290fd58c11e8",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "DeleteSession",
                "parent_span_id": "7c75cf4e166c6f31",
                "queued_behind": "0",
                "span_id": "3af339537ca14782",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "Destroy",
                "parent_span_id": "3af339537ca14782",
                "span_id": "d54a703d3ce819c7",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "HandOverName",
                "parent_span_id": "3af339537ca14782",
                "queued_behind": "0",
                "span_id": "0ba7166cceec5cf3",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
    ]
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

  Cancelling due to test failure: 3 tests still running
        PASS [   5.008s] (984/995) minimal-client box_registration::tests::resume_timeout_goes_on_and_an_absent_host_fails_closed
        PASS [   4.330s] (985/995) minimal-client tty_relay::tests::resume_after_drop_prints_line_and_nudges_winsize
        PASS [   6.131s] (986/995) minimal-client box_registration::tests::a_silent_daemon_does_not_hold_the_exit_past_the_deadline
────────────
     Summary [  45.457s] 986/995 tests run: 985 passed, 1 failed, 1 skipped
        FAIL [   2.890s] (983/995) minimal::otel_cli exec_run_attach_and_task_run_share_the_commands_trace
warning: 9/995 tests were not run due to test failure (run with --no-fail-fast to run all tests, or run with --max-fail)
error: test run failed

Outside the task's layers::

  • crates/minimal-client/Cargo.toml

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

min box cp web:notes.txt <dir> copies a file out of a stopped box. The file streams to a temporary file and is renamed into place, so a failed copy leaves no file. min box events web replays the box's events, and --follow replays and then tails them. Their errors name the box by the address the person typed and give one next step.

Notes from the agent: Revise round 1. (1) SubsystemLines now sits above the doc block of append_daemon_error. (2) The library replaces read_box_file with open_box_file, which returns a streaming sftp file or a BoxFileError (Unavailable, NotFound, PermissionDenied, Refused). The CLI logs the internal detail with tracing::warn and prints new errors that are not wrapped around the library chain, so the path prints once. Examples: Box web has no file gone.txt. and Could not read notes.txt from box web. Run min box show web to check that the box still exists.. The events errors (open refused, early end, undecodable line, Failed update) follow the same rules. Under --parent they name a child by its name, or by its id when it has none. (3) The <local> placeholder now shows the destination. (4) min box cp reads in 64 KiB chunks into .<name>.min-cp-<pid> beside the destination with tokio::fs, then renames it into place. A drop guard removes the temporary file on failure. The test still checks that gone.txt is not created, and now also checks that no temporary file is left. (5) The new signatures in lib.rs and events.rs say boxes::BoxId. (6) The unreachable Name a box bail is replaced by an expect on the clap ArgGroup invariant. Not changed: the Box <id> has no child boxes on host <host>. note still prints the box id, because the directive scoped item 2 to errors and said to change nothing else. clippy-strict was not run.

@agent-137
Copy link
Copy Markdown
Member

/revise The six items are done. The streaming rewrite brought in three defects. Fix them at head d9242f8:

A. A wrong remedy for a directory source (crates/minimal/src/cmd/boxes/events.rs ~365, and the Refused arm ~348). min box cp web:/etc ./x opens the directory, the first read fails with EISDIR, and the user gets "Could not read all of /etc from box web, so min did not write ./x. Run min box show web to check that the box still exists." The box exists. The path is a directory. After the open, check the file's metadata and say plainly that the path is a directory. On the other open failures, say only what the code proves. Drop the box-gone step where the failure does not show the box is gone (AGENTS.md: say only what the code can prove).

B. The temp file name can collide and follows symlinks (events.rs ~356, ~405). .{name}.min-cp-{pid} is opened with File::create, which truncates and follows a symlink. Two copies from sandboxes with separate PID namespaces can share a pid. A planted symlink at that name gets written through. Create it with OpenOptions::new().write(true).create_new(true) and a random suffix, or use tempfile::NamedTempFile::new_in(dir) if the crate is already a dependency. Do not add a dependency for this.

C. The cleanup path is not tested (tests.rs ~7394). The only failure case errors before the temp file exists. Add a case that fails after the temp file was created, for example a directory source (A). Check that the destination directory holds no temp file afterwards.

In the PR body, add one line on the behaviour change at an existing destination. The rename replaces a symlink or an existing file with a new file at the default mode, where std::fs::write wrote through the link and kept the mode. Change nothing else.

…file

A directory source now says the path is a directory. Open and read
failures no longer suggest the box is gone. The temporary file is
created with O_EXCL under a random name, and a test covers its cleanup.
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 2 of 10 on a /revise note, pushed as ba2cc2574d34.

line exit tail
cargo nextest run -p minimal-client session_subsystem_channel_streams_reply_lines 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-b958... Compiling args v0.0.1 (/workbench/crates/args) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling paths v0.0.1 (/workbench/crates/paths) Compiling client-config v0.0.1 (/wo
cargo nextest run -p minimal box_cp_reads_stopped_box_as_box_017 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-2559... 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 minimal events_jsonl_replay_schema_fields 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-734e... 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 minimal events_follow_replays_then_tails 0 Applying loadouts: default Running task runtime-implement-test-3 in session task-runtime-implement-test-3-7a24... 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 minimal events_parent_merges_children 0 Applying loadouts: default Running task runtime-implement-test-4 in session task-runtime-implement-test-4-698b... 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 exited 100 under rustc 1.99.0, which the repository does not pin (1.99.1 in rust-toolchain.toml); the PR is not a draft on that alone — CI, which honours the pin, is the gate.

        "span_id": "9091fb24e9f1cd35",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "GetPaths",
                "parent_span_id": "fbe1e11d603bca6d",
                "span_id": "88748c64c1e6d436",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "GetRecord",
                "parent_span_id": "fbe1e11d603bca6d",
                "queued_behind": "0",
                "span_id": "e006b2ef0c5ecb0f",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "DeleteSession",
                "parent_span_id": "fbe1e11d603bca6d",
                "queued_behind": "0",
                "span_id": "3f372d8033618a10",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "Destroy",
                "parent_span_id": "3f372d8033618a10",
                "span_id": "efc2b4b5aef84d16",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "HandOverName",
                "parent_span_id": "3f372d8033618a10",
                "queued_behind": "0",
                "span_id": "da17e85255a48ffa",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
    ]
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

  Cancelling due to test failure: 3 tests still running
        PASS [   5.009s] ( 998/1009) minimal-client box_registration::tests::resume_timeout_goes_on_and_an_absent_host_fails_closed
        PASS [   4.315s] ( 999/1009) minimal-client tty_relay::tests::resume_after_drop_prints_line_and_nudges_winsize
        PASS [   6.249s] (1000/1009) minimal-client box_registration::tests::a_silent_daemon_does_not_hold_the_exit_past_the_deadline
────────────
     Summary [  46.058s] 1000/1009 tests run: 999 passed, 1 failed, 1 skipped
        FAIL [   2.916s] ( 997/1009) minimal::otel_cli exec_run_attach_and_task_run_share_the_commands_trace
warning: 9/1009 tests were not run due to test failure (run with --no-fail-fast to run all tests, or run with --max-fail)
error: test run failed

Outside the task's layers::

  • crates/minimal-client/Cargo.toml

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

A person can run min box cp web:notes.txt ./dir to copy a file out of a stopped box. min box cp web:/workbench ./dir now says that /workbench is a directory and shows the command for one file in it. A failed copy leaves no temporary file in the destination directory. min box events replays and follows the box's events.

Notes from the agent: A: After the open, copy_box_file reads the file metadata. A directory gives " in box is a directory, and min box cp copies one file. Run min box cp <box>:<path>/<file> <target>." Unavailable and Refused at open now say only "Could not open in box ." The read failure no longer tells the person to check that the box exists. B: PartialFile is gone. The temporary file comes from tempfile::Builder::tempfile_in(dir), which is already a dependency of the crate. It has a random suffix, opens with O_EXCL, and removes itself on drop. TempPath::persist renames it into place. On unix the builder asks for mode 0666, so the umask applies as it did with File::create, and the copy does not get the 0600 of a temp file. C: box_cp_reads_stopped_box_as_box_017 now copies the directory /workbench. That fails after the temp file exists. The test then checks the exact message and that the destination directory holds only the two earlier files. PR body: the coder cannot edit it. Add this line: "At an existing destination, min box cp now renames a new file into place. A symlink or an existing file there is replaced by a regular file with the default mode, where std::fs::write wrote through the link and kept the old mode." I did not run just clippy-strict.

@agent-137
agent-137 marked this pull request as ready for review November 4, 2026 23:37
@agent-137
agent-137 requested a review from a team as a code owner November 4, 2026 23:37
@agent-137 agent-137 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 ba2cc25 against #2908 (BCLI-062, BCLI-016), with the library layer. min box events streams READ_EVENTS_SUBSYSTEM lines (follow and --parent) as min/v1/event objects; min box cp reads a retained box's file over sftp in 64 KiB chunks into an O_EXCL temp file beside the destination (tempfile, already a dependency), flushes, and renames it into place, removing the temp file on every error path. A directory source is named as one before any read. Errors name the box by the address the user typed and keep subsystem/uuid detail in the log.

@agent-137
Copy link
Copy Markdown
Member

/review

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

Reviewed: 1 finding, none blocking at e5006889d94b.

Address the comments on the diff.

Clobber: none seen.

@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 — approved

Verdict APPROVE — No findings.

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

@minimal-foundry minimal-foundry Bot added the review:approved minimal-review approved the current head label Nov 4, 2026
@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 · 5 conditions · the full ruling

This change copies a file out of a stopped box, and it replays and follows box events, as lines 501 and 503 give. The placement and the transport conform. Four outputs do not match the design. A failure under -o jsonl is not the min/v1/error object of line 383 (C1). ts is integer milliseconds, but line 381 gives RFC 3339 UTC (C2). --follow has no -f, which line 482 lists (C3). A missing file in min box cp ends with exit 1, not 4 (C4). The fixed set of children under --parent --follow needs a filed issue and an interim comment (C5).

Outside the diff

  • crates/minimal/src/main.rs:517: C1 · Add an arm for BoxCommand::Events with -o jsonl, beside the Audit arm. Line 383 says that a machine-mode failure is one min/v1/error object on stderr.

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

Comment thread crates/minimal/src/cmd/boxes/events.rs Resolved
Comment thread crates/minimal/src/tests.rs Resolved
Comment thread crates/minimal/src/cli.rs Resolved
Comment thread crates/minimal/src/cmd/boxes/events.rs Resolved
Comment thread crates/minimal/src/cmd/boxes/events.rs Resolved
@agent-137
Copy link
Copy Markdown
Member

/revise Arch fails at ba2cc25 with conditions C2-C5. All are checked against the code. Fix C2-C4 and the jsonl error object. C5 is residual #3024.

C2 (threads z2zM4, z2zM-): machine-mode timestamps are RFC 3339 UTC (architecture -o json rule). EventRow is #[serde(transparent)] over BoxEvent, so ts goes out as integer milliseconds. Give EventRow its own Serialize, or a mirror struct, that writes ts as to_rfc3339_opts(SecondsFormat::Millis, true), the same conversion text() already does. Leave BoxEvent alone. In tests.rs (~7867 and ~8034-8038), parse ts with DateTime::parse_from_rfc3339 and check the order on the parsed values.

C3 (thread z2zNC): --follow takes -f too (cli.rs ~489: #[arg(short = 'f', long)]).

C4 (thread z2zNK): min box cp exits 1 for a missing file (events.rs ~342-345 fall through failure_exit_code in main.rs). BCLI-034 requires the exit-code table: 4 for not found, 5 for permission denied. Return a typed refusal with the same message text, and downcast it in failure_exit_code, as BoxAddressRefusal is. Add tests for both codes.

Also: machine_output_mode in main.rs does not list min box events -o jsonl, so a failure under -o jsonl prints a plain line instead of a min/v1/error object. Add it, with a test.

C5 (thread z2zNO): residual #3024. Add an // Interim: comment at events.rs ~87 naming #3024 and the condition for removing it (see box_audit.rs ~114), and name #3024 in the PR body.

Change nothing else.

Write ts in min box events -o jsonl as an RFC 3339 UTC string, give
--follow its -f short form, and end min box cp with 4 for a missing file
and 5 for a denied read. A min box events -o jsonl failure is now one
min/v1/error object. Mark the one-time child read under --parent as
interim, ended by #3024.
…-out-of-a-box-and-read-its-events

# Conflicts:
#	crates/minimal/src/tests.rs
@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 compare fixes each point of the last ruling. Event ts is now RFC 3339 UTC, as Output conventions line 381 requires. --follow has -f (line 482). A -o jsonl failure is one min/v1/error object (line 383). min box cp ends with 4 or 5 per the exit-code table. The one-time child read under --parent --follow stays a residual on #3024.

Ruled at 8d3c001 against gominimal/arch@7a86ec7bb3c0 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.

minimal-review — advisory review

Verdict COMMENT — 1 finding, 1 inline.

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

Comment thread crates/minimal/src/cmd/boxes/events.rs Resolved
@minimal-foundry minimal-foundry Bot removed the review:approved minimal-review approved the current head label Nov 5, 2026
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 3 of 10 on a /revise note, pushed as 8d3c001f0855.

Resolved conflicts in: crates/minimal/src/tests.rs

line exit tail
cargo nextest run -p minimal-client session_subsystem_channel_streams_reply_lines 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-cf22... Compiling args v0.0.1 (/workbench/crates/args) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling paths v0.0.1 (/workbench/crates/paths) Compiling client-config v0.0.1 (/wo
cargo nextest run -p minimal box_cp_reads_stopped_box_as_box_017 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-8dd5... Compiling paths v0.0.1 (/workbench/crates/paths) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling args v0.0.1 (/workbench/crates/args) Compiling client-config v0.0.1 (/wo
cargo nextest run -p minimal events_jsonl_replay_schema_fields 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-3312... 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 minimal events_follow_replays_then_tails 0 Applying loadouts: default Running task runtime-implement-test-3 in session task-runtime-implement-test-3-f1b8... 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 minimal events_parent_merges_children 0 Applying loadouts: default Running task runtime-implement-test-4 in session task-runtime-implement-test-4-1fc2... 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 exited 100 under rustc 1.99.0, which the repository does not pin (1.99.1 in rust-toolchain.toml); the PR is not a draft on that alone — CI, which honours the pin, is the gate.

       "span_id": "0f2a94ef42944504",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "GetPaths",
                "parent_span_id": "b14499766836f3b4",
                "span_id": "c0ff5bbe9134e9c9",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "GetRecord",
                "parent_span_id": "b14499766836f3b4",
                "queued_behind": "0",
                "span_id": "d96ecf4c8257fcae",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "DeleteSession",
                "parent_span_id": "b14499766836f3b4",
                "queued_behind": "0",
                "span_id": "15e549eedb3ce9da",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "Destroy",
                "parent_span_id": "15e549eedb3ce9da",
                "span_id": "8561da6e18518b13",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "HandOverName",
                "parent_span_id": "15e549eedb3ce9da",
                "queued_behind": "0",
                "span_id": "9f231ec15d125f85",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
    ]
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

  Cancelling due to test failure: 3 tests still running
        PASS [   5.018s] (1000/1011) minimal-client box_registration::tests::resume_timeout_goes_on_and_an_absent_host_fails_closed
        PASS [   4.481s] (1001/1011) minimal-client tty_relay::tests::resume_after_drop_prints_line_and_nudges_winsize
        PASS [   6.158s] (1002/1011) minimal-client box_registration::tests::a_silent_daemon_does_not_hold_the_exit_past_the_deadline
────────────
     Summary [  54.974s] 1002/1011 tests run: 1001 passed, 1 failed, 1 skipped
        FAIL [   3.255s] ( 999/1011) minimal::otel_cli exec_run_attach_and_task_run_share_the_commands_trace
warning: 9/1011 tests were not run due to test failure (run with --no-fail-fast to run all tests, or run with --max-fail)
error: test run failed

Outside the task's layers::

  • crates/minimal-client/Cargo.toml
  • crates/minimal/src/main.rs

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

Review threads the coder judged:

  • 👍 valid — crates/minimal/src/cmd/boxes/events.rs: EventRow now has its own Serialize through a mirror struct that writes ts as to_rfc3339_opts(Millis, true). BoxEvent did not change.
  • 👍 valid — crates/minimal/src/tests.rs: Both tests now parse ts with DateTime::parse_from_rfc3339, check the Z suffix, and check the order on the parsed values.
  • 👍 valid — crates/minimal/src/cli.rs: --follow on min box events is now #[arg(short = 'f', long)].
  • 👍 valid — crates/minimal/src/cmd/boxes/events.rs: min box cp returns a typed BoxFileRefusal with the same text. failure_exit_code maps it to 4 (not found) and 5 (permission denied), and tests pin both codes.
  • 👍 valid — crates/minimal/src/cmd/boxes/events.rs:177: Added an Interim: comment at the one-time child read that names BCLI: min box events --parent --follow picks up children created after it starts (T24 residual, arch C5) #3024. It says to remove the comment when the follow picks up new children.

min box cp <box>:<path> <local> copies a file out of a stopped box. It ends with 4 for a missing file and 5 for a denied read. min box events <box> replays the events of a box, and -f/--follow then keeps showing new ones. With -o jsonl, each line has ts as an RFC 3339 UTC string, and a failure is one min/v1/error object.

Notes from the agent: crates/minimal/src/main.rs is outside the task's layers. The directive asked for it by name: failure_exit_code downcasts the new BoxFileRefusal, and machine_output_mode lists min box events -o jsonl. Both have unit tests there. The exit 5 path has only a unit test in main.rs, because the in-process harness cannot easily make a file that the box cannot read. The exit 4 path is also checked in box_cp_reads_stopped_box_as_box_017. The PR body must name #3024 for C5. The runner writes the body, so I could not add it.

@minimal-foundry
minimal-foundry Bot marked this pull request as draft November 5, 2026 00:27
@agent-137
agent-137 marked this pull request as ready for review November 5, 2026 00:40
@agent-137 agent-137 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.

Re-reviewed at 8d3c001. abe9acb answers arch C2-C5: ts is RFC 3339 UTC with milliseconds (tests parse it), --follow takes -f, min box cp exits 4/5 through BoxFileRefusal, min box events -o jsonl is a machine mode so failures print a min/v1/error object, and the --parent follow gap is residual #3024 with an Interim comment. Arch passes this head. commitlint's subject-case failure is not a required check.

@agent-137
agent-137 added this pull request to the merge queue Nov 5, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Nov 5, 2026
@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

This head adds one merge of main and no new branch commit. The merge conflicts in cli.rs and cmd/mod.rs, and each resolution keeps the arms from both sides. The branch's own changes are the same as on 8d3c001f, so that PASS stands. min box events uses the shared event type, so it also prints the renamed event that #3008 adds (architecture.md line 382). Issue #3024 owns the --parent --follow residual.

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

@minimal-foundry
Copy link
Copy Markdown
Contributor Author

A merge of main before round 4, not counted against the cap (1 of 3), pushed as e5006889d94b.

Resolved conflicts in: crates/minimal/src/cli.rs, crates/minimal/src/cmd/mod.rs

line exit tail
cargo nextest run -p minimal-client session_subsystem_channel_streams_reply_lines 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-6271... Compiling args v0.0.1 (/workbench/crates/args) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling paths v0.0.1 (/workbench/crates/paths) Compiling client-config v0.0.1 (/wo
cargo nextest run -p minimal box_cp_reads_stopped_box_as_box_017 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-c3fc... 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 minimal events_jsonl_replay_schema_fields 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-1d7e... Compiling stdlib v0.0.22 (/workbench/crates/stdlib) Compiling version v0.8.0 (/workbench/crates/version) C 8 ompiling 10BC8 decode v0.0.1 (/workbench/crates/decode) Compiling mlog v0.
cargo nextest run -p minimal events_follow_replays_then_tails 0 Applying loadouts: default Running task runtime-implement-test-3 in session task-runtime-implement-test-3-ba6e... 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 minimal events_parent_merges_children 0 Applying loadouts: default Running task runtime-implement-test-4 in session task-runtime-implement-test-4-4d04... 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 exited 100 under rustc 1.99.0, which the repository does not pin (1.99.1 in rust-toolchain.toml); the PR is not a draft on that alone — CI, which honours the pin, is the gate.

       "span_id": "7df55cfcee0c1d28",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "GetPaths",
                "parent_span_id": "aa76ae02d198830e",
                "span_id": "ee141f2981af4786",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "GetRecord",
                "parent_span_id": "aa76ae02d198830e",
                "queued_behind": "0",
                "span_id": "782755452f9e3f28",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "DeleteSession",
                "parent_span_id": "aa76ae02d198830e",
                "queued_behind": "0",
                "span_id": "8dcf58377eb6eb5e",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "Destroy",
                "parent_span_id": "8dcf58377eb6eb5e",
                "span_id": "195ef8f9ec3992fd",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "HandOverName",
                "parent_span_id": "8dcf58377eb6eb5e",
                "queued_behind": "0",
                "span_id": "b18ed615d14425af",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
    ]
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

  Cancelling due to test failure: 3 tests still running
        PASS [   5.019s] (1005/1016) minimal-client box_registration::tests::resume_timeout_goes_on_and_an_absent_host_fails_closed
        PASS [   4.493s] (1006/1016) minimal-client tty_relay::tests::resume_after_drop_prints_line_and_nudges_winsize
        PASS [   6.203s] (1007/1016) minimal-client box_registration::tests::a_silent_daemon_does_not_hold_the_exit_past_the_deadline
────────────
     Summary [  53.715s] 1007/1016 tests run: 1006 passed, 1 failed, 1 skipped
        FAIL [   3.119s] (1004/1016) minimal::otel_cli exec_run_attach_and_task_run_share_the_commands_trace
warning: 9/1016 tests were not run due to test failure (run with --no-fail-fast to run all tests, or run with --max-fail)
error: test run failed

Outside the task's layers::

  • crates/minimal-client/Cargo.toml
  • crates/minimal/src/main.rs

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

min box cp copies a file out of a stopped box, and min box events replays and follows its events. The branch now merges cleanly with main, so min box rename and min box prune from main sit beside them.

Notes from the agent: Conflict round only. Both conflicts were additive: main added the Rename and Prune variants of BoxCommand, and this branch added Events and Cp. The merge keeps all four variants in cli.rs and all four arms in both matches in cmd/mod.rs. cargo clippy -p minimal --all-targets is clean, and cargo fmt --check passes. The merge commit is e500688.

@minimal-foundry
minimal-foundry Bot marked this pull request as draft November 5, 2026 01:41
@agent-137
agent-137 marked this pull request as ready for review November 5, 2026 01:52
@agent-137 agent-137 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.

Re-approved at e500688: a merge of main only (the conflict with #3008); the PR's net diff against main is line-for-line the one I approved at 8d3c001.

@agent-137
agent-137 added this pull request to the merge queue Nov 5, 2026
Merged via the queue into main with commit 768cc4b Nov 5, 2026
35 of 36 checks passed
@agent-137
agent-137 deleted the agent/2908-copy-files-out-of-a-box-and-read-its-events branch November 5, 2026 02:45
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.

T24 — Copy files out of a box and read its events

1 participant

0