Skip to content

feat(cargo-each): add portable execution inputs - #176

Open
martin-kolinek wants to merge 25 commits into
mainfrom
feat/cargo-each-portable-execution
Open

martin-kolinek wants to merge 25 commits into
mainfrom
feat/cargo-each-portable-execution

Conversation

@martin-kolinek

@martin-kolinek martin-kolinek commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

🤖 Prepares cargo-each to replace shell-heavy workspace orchestration in generated tooling.

  • reads repeatable --package-file selections, including Windows-style leading UTF-8 BOMs
  • exposes lazy {workspace-rust-version} resolution and validation
  • adds bounded --jobs N execution plus machine-relative --jobs auto, with deterministic spill-backed output
  • monitors reader failures while children run and bounds descendant-held pipe draining
  • adds per-invocation --timeout with sealed process-tree containment and durable background reaping
  • performs single-pass placeholder substitution so inserted paths are never rescanned
  • preserves deterministic output across worker launch, panic, and cleanup failures

This implements the cargo-each portion of #173. It remains independent of cargo-delta #32; generated impact producers must first adopt the documented one-name@version-per-line package-file format before switching recipes to the planned examples.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: a9fc919b-99f7-4134-aad1-2116321b4e0c
Copilot AI lite review requested due to automatic review settings September 11, 2026 18:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Critical and moderate issues remain in execution cleanup, timeout handling, parsing, and placeholder expansion.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds portable package-file selection, workspace Rust-version substitution, bounded parallel execution, buffered output, timeouts, and process-tree cleanup to cargo-each.

Changes:

  • Adds repeatable --package-file inputs and lazy Rust-version validation.
  • Adds --jobs, deterministic buffered output, and per-invocation timeouts.
  • Updates tests, documentation, dependencies, and lockfile.
File summaries
File Summary
crates/cargo-each/tests/cli.rs Adds integration coverage.
crates/cargo-each/src/workspace.rs Resolves and validates workspace Rust versions.
crates/cargo-each/src/substitute.rs Adds workspace placeholder substitution; moderate issue: inserted values can be rescanned.
crates/cargo-each/src/select.rs Parses package-file selections; critical issue: malformed validation results are not propagated correctly.
crates/cargo-each/src/run.rs Implements execution and cleanup; critical issues remain around unbounded output draining and synchronous timeout termination, plus moderate issues with interrupted reads, descendant lifecycle, and placeholder ordering.
crates/cargo-each/src/plan.rs Propagates expanded invocation options.
crates/cargo-each/src/main.rs Updates CLI documentation.
crates/cargo-each/src/filter.rs Updates test fixtures.
crates/cargo-each/src/error.rs Adds typed input and configuration diagnostics.
crates/cargo-each/src/cli.rs Defines new selection and execution options.
crates/cargo-each/README.md Updates generated documentation; nit: differs from the source rustdoc artifact.
crates/cargo-each/docs/design/README.md Documents the expanded contract.
crates/cargo-each/Cargo.toml Adds required dependencies.
Cargo.lock Records dependency updates.
Review details

Suppressed comments (5)

crates/cargo-each/README.md:121

  • This generated README disagrees with its source rustdoc: crates/cargo-each/src/main.rs:111 uses member's, while this line uses member’s. Since the repository's anvil-readme-check validates generated crate READMEs, regenerate this file from the source rather than committing an artifact-only mismatch.
uses `{workspace-rust-version}`, then requires every member’s resolved

crates/cargo-each/src/run.rs:350

  • run_captured uses spawn_tree even when timeout is None, so the no-timeout parallel path reaches ProcessTree::observe, whose normal-exit contract sweeps descendants (crates/cargo-gamma-process/src/process_tree.rs:1120-1125). A command such as sh -c 'sleep 10 & exit 0' therefore has its background child killed under --jobs > 1, unlike the existing sequential Command::status path; the new --jobs contract only documents buffering. Please preserve the no-timeout child lifecycle or document this behavior explicitly.
    let mut tree = match spawn_tree(command) {
        Ok(tree) => tree,
        Err(error) => {

crates/cargo-each/src/run.rs:189

  • Workspace placeholder expansion happens after {manifest} is substituted. If the workspace/member path legally contains the literal {workspace-rust-version} text, this second pass rewrites the inserted manifest path as well, so a valid {manifest} becomes wrong. Expand the workspace token against the original command argument before inserting member-derived values; target mode needs the same ordering.
    let mut next_index = 0;

crates/cargo-each/src/run.rs:209

  • Target mode repeats the same replacement ordering: {manifest} is inserted before the workspace-token pass. A legal member path containing {workspace-rust-version} is therefore rewritten into an invalid path whenever both placeholders are used. Apply the workspace-token replacement to the original argument before inserting target/member values here as well.
        if !keep_going && outcome.outcome.result.failed() {

crates/cargo-each/src/substitute.rs:97

  • This helper is called after {manifest} and other per-package substitutions, so it also rewrites the replacement text. A workspace path such as /tmp/{workspace-rust-version}/Cargo.toml makes an original echo {manifest} invocation either fail as unresolved (when the token was not in the command) or silently alter the path. Replace this workspace token on the original argument before inserting other placeholder values, or otherwise keep inserted values from being rescanned.
fn replace_workspace_rust_version(arg: String, placeholders: &Placeholders) -> Result<String, EachError> {
    if !arg.contains(WORKSPACE_RUST_VERSION_TOKEN) {
        return Ok(arg);
    }
    let version = placeholders
        .workspace_rust_version()
        .ok_or_else(|| WorkspaceRustVersionError::new("the command uses the placeholder but its root value was not resolved".to_owned()))?;
    Ok(arg.replace(WORKSPACE_RUST_VERSION_TOKEN, version))
  • Files reviewed: 13/14 changed files
  • Comments generated: 4
  • Review effort level: Lite

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread crates/cargo-each/src/run.rs Outdated
Comment thread crates/cargo-each/src/run.rs Outdated
Comment thread crates/cargo-each/src/select.rs
Comment thread crates/cargo-each/src/run.rs Outdated
@codecov-commenter

Codecov Comments Bot (codecov-commenter) commented Sep 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.86036% with 19 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.6%. Comparing base (1910173) to head (5612063).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
crates/cargo-gamma-process/src/process_tree.rs 97.4% 13 Missing ⚠️
crates/cargo-gamma-unsafe/src/pipe.rs 93.6% 6 Missing ⚠️

❌ Your project status has failed because the head coverage (97.6%) is below the target coverage (100.0%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files
@@           Coverage Diff           @@
##            main    #176     +/-   ##
=======================================
  Coverage   97.6%   97.6%             
=======================================
  Files        304     306      +2     
  Lines      69683   71310   +1627     
=======================================
+ Hits       68016   69634   +1618     
- Misses      1667    1676      +9     
Flag Coverage Δ
linux 97.6% <97.9%> (+<0.1%) ⬆️
linux-arm 97.6% <97.9%> (+<0.1%) ⬆️
scheduled ?
windows 97.8% <97.9%> (+<0.1%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: a9fc919b-99f7-4134-aad1-2116321b4e0c
Copilot AI review requested due to automatic review settings September 11, 2026 21:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Unresolved process-execution and resource-handling issues block approval.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (5)

crates/cargo-each/src/run.rs:361

  • The parallel runner unconditionally sets child stdin to Stdio::null(), whereas the existing sequential Command::status() path inherits the caller's stdin. Consequently, opting into --jobs > 1 changes commands that consume input to see EOF immediately, which can silently break otherwise valid commands. Preserve the prior stdin behavior or make this non-interactive contract explicit and reject/document input-consuming commands.
    let _ = command.stdin(Stdio::null()).stdout(Stdio::piped()).stderr(Stdio::piped());

crates/cargo-each/src/run.rs:213

  • The scheduler retains every BufferedOutcome in outcomes until the entire plan has finished, and only emits them afterward. With --keep-going this makes captured stdout/stderr grow with the total output of every selected package rather than with --jobs; a workspace-wide test run can therefore exhaust cargo-each's memory even though concurrency is bounded. Spool completed output to per-invocation temporary storage, or otherwise add a bounded retention strategy that still preserves plan-order emission.
    let mut outcomes = Vec::with_capacity(invocations.len());
    while let Some(outcome) = wait_for_worker(&mut workers) {
        if failure_stops_launching(keep_going, outcome.outcome.result.failed()) {
            stop_launching = true;
        }
        outcomes.push(outcome);

crates/cargo-each/src/run.rs:574

  • The captured reader treats every read error as fatal, including ErrorKind::Interrupted. A signal can transiently interrupt a pipe read, and the repository's sibling process-tree reader explicitly retries this condition (crates/cargo-gamma-process/src/process_tree.rs:1359-1362); here it instead turns a successful invocation into an infrastructure failure and drops the remaining output. Retry interrupted reads before returning other errors.
            let read = match stream.read(&mut chunk)? {
                0 => return Ok(()),
                read => read,
            };

crates/cargo-each/src/run.rs:414

  • This no-timeout branch still waits through ProcessTree::observe, whose contract kills surviving descendants when the leader exits. Consequently, requesting --jobs > 1 changes a successful command's child-process semantics: a command that intentionally leaves a background child running is terminated, whereas the default sequential path uses Command::status() and leaves it running. The documented process-tree termination is scoped to timed-out commands; either preserve the no-timeout behavior or document and test this additional ownership of descendants.
    let tree_outcome = match timeout {
        Some(timeout) => wait_for_tree(&mut tree, timeout),
        None => wait_for_tree_without_timeout(&mut tree),
    };

crates/cargo-each/src/substitute.rs:213

  • This comment is no longer accurate: {workspace-rust-version} is a valid placeholder in this branch and is substituted on line 217. Narrow the statement to the {packages} token so it describes the actual validation guarantee.
            Placeholders::Once { packages, .. } => {
                // Validation above guarantees each arg is either exactly
                // `{packages}` or contains no placeholder token at all.
  • Files reviewed: 13/14 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread crates/cargo-each/src/run.rs
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: a9fc919b-99f7-4134-aad1-2116321b4e0c
Copilot AI review requested due to automatic review settings September 11, 2026 22:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Unresolved process cleanup, scheduling, reader-lifecycle, and capacity-handling issues block approval.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (3)

crates/cargo-each/src/run.rs:228

  • With keep_going enabled, a worker-thread creation error sets stop_launching, so pending invocations are never attempted and the function returns an AppError instead of an invocation outcome. That contradicts the documented complete-plan/exit-1 contract and differs from ordinary command-spawn failures, which are recorded per invocation; record this index as an infrastructure outcome and continue scheduling under keep_going (while retaining the fail-fast abort).
            Err(error) => {
                launch_error = Some(error);
                stop_launching = true;
            }

crates/cargo-each/src/run.rs:759

  • When the drain deadline expires, retaining is set false, but the reader may already be blocked in stream.read; dropping the JoinHandle only detaches that thread and leaves its pipe handle alive. A descendant that keeps the pipe open indefinitely therefore leaks one blocked thread and read handle per stream, so repeated --jobs/--keep-going invocations can eventually exhaust process resources. Close/interrupt the stream before detaching, or use a reader ownership design with a cancellable read.
    drop(thread);

crates/cargo-each/src/run.rs:191

  • cargo_gamma_process::capacity() is the Unix limit for watched process-tree slots, but this branch applies it even when timeout is None; in that mode run_captured creates an ordinary Child and never allocates a ProcessTree. On Unix, requesting --jobs above that unrelated watcher capacity is therefore silently throttled. Apply this cap only when timed containment is actually selected.
    let worker_count = jobs.get().min(invocations.len()).min(cargo_gamma_process::capacity().max(1));
  • Files reviewed: 17/18 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread crates/cargo-each/src/run.rs Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: a9fc919b-99f7-4134-aad1-2116321b4e0c
Copilot AI review requested due to automatic review settings September 11, 2026 23:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Parallel output capture and process cleanup require fixes, and stdin behavior needs a defined contract.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (4)

crates/cargo-each/src/run.rs:559

  • This error branch drops the local Child immediately and returns; unlike the setup-failure paths above, it never calls terminate_ordinary_child. If a wait error occurs before the leader is reaped, the no-timeout parallel path can report an infrastructure failure while the invocation is still running. Run the same bounded cleanup here and retain the wait error, plus any cleanup error, in the result.
                    Err(error) => TreeOutcome::new(InvocationResult::Infrastructure(format!(
                        "failed to wait for child process: {error}"
                    ))),

crates/cargo-each/src/run.rs:191

  • cargo_gamma_process::capacity() is the Unix interrupt-watch limit, but the timeout == None branch launches ordinary Childs directly at lines 364-369 and never consumes those slots. Capping every parallel run by this value unnecessarily throttles ordinary --jobs N executions (and limits them to the registry capacity on Unix); apply the cap only when timeout-based ProcessTrees are used.
    let worker_count = jobs.get().min(invocations.len()).min(cargo_gamma_process::capacity().max(1));

crates/cargo-each/src/run.rs:774

  • On the drain deadline this only flips a flag and drops the JoinHandle; it cannot interrupt the reader's already-blocked stream.read, because the stream is owned by that detached thread. A descendant that keeps a pipe open indefinitely therefore leaves one live thread and file descriptor per timed-out stream, so repeated --keep-going invocations can accumulate resources despite the one-second bound. The reader needs a cancellable/closable ownership path, or the resource lifetime must be bounded another way.
        Err(mpsc::RecvTimeoutError::Timeout) => {
            retaining.store(false, Ordering::Release);
            Some(format!(
                "child {stream} remained open for more than {} ms after the {boundary} completed; partial output was retained",
                grace.as_millis()

crates/cargo-each/src/run.rs:363

  • Parallel workers unconditionally replace stdin with Stdio::null(), while the sequential run_streamed path leaves stdin inherited. Consequently the same arbitrary command receives caller input with --jobs 1 but EOF with --jobs > 1, and the new CLI/docs do not state this change in input semantics. Please define/document the stdin contract or provide an explicit handling policy for commands that read stdin.
    let _ = command.stdin(Stdio::null()).stdout(Stdio::piped()).stderr(Stdio::piped());
  • Files reviewed: 17/18 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread crates/cargo-each/src/run.rs Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: a9fc919b-99f7-4134-aad1-2116321b4e0c

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Four unresolved moderate issues in run.rs affect stdin behavior, parallelism limits, child cleanup, and reader-thread resources.

Review details

Suppressed comments (4)

crates/cargo-each/src/run.rs:371

  • The parallel runner unconditionally replaces the caller's stdin with Stdio::null(). A command run with --jobs > 1 therefore receives EOF even when the plan contains only one invocation, whereas the sequential path leaves stdin inherited (run_streamed at crates/cargo-each/src/run.rs:327-335). This is an observable behavior change for commands that read stdin; either preserve stdin for the single-invocation case and define/reject it for multiple workers, or document and test the new contract.
    let _ = command.stdin(Stdio::null()).stdout(Stdio::piped()).stderr(Stdio::piped());

crates/cargo-each/src/run.rs:191

  • This clamps every parallel run to the process-tree interrupt registry capacity, even when timeout is None. In the no-timeout branch below, workers use ordinary Command::spawn (crates/cargo-each/src/run.rs:372-377) and do not consume process-tree watch slots, so an explicit --jobs larger than that capacity is silently serialized to the platform ceiling for no reason. Apply the capacity limit only to timed/contained workers so ordinary parallel runs can use the requested bound.
    let worker_count = jobs.get().min(invocations.len()).min(cargo_gamma_process::capacity().max(1));

crates/cargo-each/src/run.rs:567

  • If Child::wait() returns an error, this arm has already taken the child out of CapturedProcess and returns without terminating it. The subsequent drop(process) therefore sees Ordinary(None), so a still-running child (and its descendants) can outlive cargo-each; the injected CaptureFault::WaitFailure exercises this path. Attempt bounded termination here and preserve any cleanup error alongside the wait error.
                match waited {
                    Ok(status) => TreeOutcome::new(InvocationResult::Exited(status)),
                    Err(error) => TreeOutcome::new(InvocationResult::Infrastructure(format!(
                        "failed to wait for child process: {error}"
                    ))),

crates/cargo-each/src/run.rs:783

  • This only flips retaining; if a background or escaped descendant keeps the pipe open, the reader can remain blocked in stream.read, and the JoinHandle is then dropped below without stopping that thread. Each such invocation can therefore leave a live reader thread and pipe handle behind even though the scheduler returns, so repeated parallel runs can exhaust process resources. Please make the pipe read cancellable/closeable from the owning side, or use a bounded reusable reader strategy, before detaching.
        Err(mpsc::RecvTimeoutError::Timeout) => {
            retaining.store(false, Ordering::Release);
            Some(format!(
                "child {stream} remained open for more than {} ms after the {boundary} completed; partial output was retained",
                grace.as_millis()
            ))
  • Files reviewed: 17/18 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: a9fc919b-99f7-4134-aad1-2116321b4e0c
Copilot AI review requested due to automatic review settings September 12, 2026 02:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

One critical and four moderate unresolved findings affect process reaping, timeout semantics, memory bounds, stdin behavior, and reader cleanup.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (3)

crates/cargo-each/src/run.rs:215

  • The 1 MiB limit is only per stream, while every BufferedOutcome is retained in outcomes until all workers finish and deterministic emission begins. A large plan whose commands each produce less than 1 MiB can therefore accumulate output proportional to the entire plan, defeating the bounded-memory expectation in the design (crates/cargo-each/docs/design/README.md:305-320) and potentially exhausting the runner. Please drain completed outcomes in plan order as soon as the next contiguous result is available, or enforce a global output budget/spill policy.
    let mut outcomes = Vec::with_capacity(invocations.len());
    while let Some(outcome) = wait_for_worker(&mut workers) {
        if failure_stops_launching(keep_going, outcome.outcome.result.failed()) {
            stop_launching = true;
        }

crates/cargo-each/src/run.rs:372

  • Parallel execution unconditionally replaces the caller's stdin with Stdio::null(), so a command that reads stdin receives EOF when --jobs > 1; the sequential path still inherits stdin through Command::status(). That makes execution depend on the concurrency setting, while the documented parallel behavior says commands retain ordinary direct-child semantics (src/main.rs:117-124). Preserve an explicit stdin policy across modes or document/reject this incompatible behavior.
    let _ = command.stdin(Stdio::null()).stdout(Stdio::piped()).stderr(Stdio::piped());

crates/cargo-gamma-process/src/process_tree.rs:1357

  • At the deadline, a failed kill returns an error with error.kind() (for example, PermissionDenied) instead of io::ErrorKind::TimedOut, even though this method documents deadline results as timed-out errors. Preserve TimedOut as the error kind while retaining the kill failure in the message so callers can distinguish a bounded deadline from an immediate cleanup failure.
                return Err(kill_error.take().map_or_else(
                    || io::Error::new(io::ErrorKind::TimedOut, deadline_error.clone()),
                    |error| io::Error::new(error.kind(), format!("{error}; {deadline_error}")),
                ));
  • Files reviewed: 17/18 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment thread crates/cargo-gamma-process/src/process_tree.rs Outdated
Comment thread crates/cargo-each/src/run.rs Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: a9fc919b-99f7-4134-aad1-2116321b4e0c
Copilot AI review requested due to automatic review settings September 12, 2026 04:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

A critical unbounded wait and a moderate --keep-going test mismatch remain unresolved.

Get a fresh assessment by requesting another Copilot review.

Review details
  • Files reviewed: 17/18 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment thread crates/cargo-each/src/run.rs Outdated
Comment thread crates/cargo-each/tests/cli.rs
Exercise the timed streamed runner through an injected test spawner so Linux hosts without delegated cgroups validate the logic without weakening production timeout refusal.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: a9fc919b-99f7-4134-aad1-2116321b4e0c

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

A critical interrupted-wait handling issue remains, and the process-tree changes require human review.

Review details
  • Files reviewed: 21/23 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread crates/cargo-gamma-process/src/process_tree.rs
Copilot AI review requested due to automatic review settings September 16, 2026 19:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Reaper startup performs potentially blocking work inside the Unix interrupt spawn window, which can defer terminal-signal handling under resource contention.

Review details

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

crates/cargo-gamma-process/src/process_tree.rs:450

  • ensure_reaper() now runs while the Unix SpawnGuard::spawning window is held. That window's contract explicitly forbids covering work that cannot create a child because an interrupt is deferred (crates/cargo-gamma-unsafe/src/interrupt.rs:35-42); creating the reaper thread can block or fail before Command::spawn, so resource contention can delay terminal-signal handling. Move this preflight before window() is opened (or close and reopen the guard around it).
  • Files reviewed: 21/23 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Keep children queued when try_wait is interrupted while continuing to warn and stop tracking on permanent observation failures.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: a9fc919b-99f7-4134-aad1-2116321b4e0c
Copilot AI review requested due to automatic review settings September 17, 2026 09:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The new public cargo-gamma-unsafe pipe surface is not documented in that crate’s design or implementation docs.

Review details

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

crates/cargo-gamma-unsafe/src/lib.rs:78

  • This adds a new public OS-specific interface for interruptible child-pipe reads, but cargo-gamma-unsafe/docs/DESIGN.md still describes the crate only in terms of process-subtree termination and cgroup/job containment (:8-15, :21-30, :37-41). The repository rule in AGENTS.md:13-17 and :73-76 requires externally observable behavior to be documented with the implementation; add the pipe's portability and readiness/ownership contract to the unsafe crate's design/implementation docs.
  • Files reviewed: 21/23 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Also give the sequential timeout integration probe enough startup time under instrumented CI while preserving its five-second timeout behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: a9fc919b-99f7-4134-aad1-2116321b4e0c
Copilot AI review requested due to automatic review settings September 17, 2026 12:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

It changes cross-platform process containment, concurrent scheduling, pipe I/O, and child reaping, requiring final human validation.

Review details
  • Files reviewed: 21/23 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@wukchung martinhavelka (wukchung) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Independent multi-model review with per-finding verification against the current branch. Four findings survived; two are reproduced against a build of this branch. Everything else raised in earlier rounds verified as correctly and completely addressed.

Comment thread crates/cargo-each/src/run.rs
Comment thread crates/cargo-each/src/run.rs
Comment thread crates/cargo-gamma-process/src/process_tree.rs
Use effective concurrency for stream mode, defer workspace-version resolution for empty plans, keep cancelled output recovery nonblocking, and make detached reaper diagnostics restart-safe.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: a9fc919b-99f7-4134-aad1-2116321b4e0c
Copilot AI review requested due to automatic review settings September 18, 2026 11:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

It spans cross-platform unsafe pipe handling, process-tree lifecycle guarantees, concurrent scheduling, and timeout cleanup.

Review details
  • Files reviewed: 21/23 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Comment thread crates/cargo-gamma-process/src/process_tree.rs Outdated
Restore the Unix test Write import and use repository-approved standard-input terminology in public documentation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: a9fc919b-99f7-4134-aad1-2116321b4e0c
Copilot AI review requested due to automatic review settings September 18, 2026 12:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Reaper handoff failures can discard child ownership, and one bounded-termination failure path can fall into blocking ProcessTree::Drop cleanup.

Get a fresh assessment by requesting another Copilot review.

Review details
  • Files reviewed: 21/23 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment thread crates/cargo-each/src/run.rs Outdated
Comment thread crates/cargo-gamma-process/src/process_tree.rs
@github-actions

github-actions Bot commented Sep 18, 2026

Copy link
Copy Markdown

⚠️ SemVer check advisory

Inconclusive comparisons

cargo semver-checks could not complete the following comparisons. These failures are informational because an unbuildable baseline is not evidence of a breaking API change.

cargo-aprz-lib (exit 101)

     Cloning 1910173d2c5eb596341960371a2ca25957292c4d
    Building cargo-aprz-lib v1.1.2 (current)
error: running cargo-doc on crate 'cargo-aprz-lib' failed with output:
-----
   Compiling proc-macro2 v1.0.107
   Compiling quote v1.0.47
   Compiling unicode-ident v1.0.26
   Compiling libc v0.2.189
    Checking memchr v2.8.3
    Checking cfg-if v1.0.5
    Checking smallvec v1.16.1
    Checking bytes v1.12.1
    Checking regex-syntax v0.8.11
    Checking once_cell v1.21.4
   Compiling syn v3.0.6
   Compiling thiserror v2.0.20
    Checking stable_deref_trait v1.2.1
   Compiling crc32fast v1.5.2
    Checking scopeguard v1.2.0
   Compiling crossbeam-utils v0.8.23
    Checking regex-automata v0.4.18
    Checking zlib-rs v0.6.8
   Compiling parking_lot_core v0.9.12
   Compiling getrandom v0.4.3
    Checking bitflags v2.13.2
    Checking gix-trace v0.1.21
    Checking fastrand v2.5.0
    Checking lock_api v0.4.14
   Compiling serde_core v1.0.229
    Checking tinyvec v1.13.3
    Checking bstr v1.13.1
    Checking parking_lot v0.12.5
    Checking unicode-normalization v0.1.25
    Checking itoa v1.0.18
    Checking gix-validate v0.11.4
    Checking gix-utils v0.3.6
    Checking crossbeam-channel v0.5.17
    Checking same-file v1.0.6
    Checking walkdir v2.5.0
    Checking foldhash v0.2.0
    Checking byteorder v1.5.0
    Checking equivalent v1.0.2
    Checking allocator-api2 v0.2.21
   Compiling shlex v2.0.1
   Compiling find-msvc-tools v0.1.13
    Checking prodash v31.0.0
   Compiling jobserver v0.1.35
    Checking gix-error v0.2.5
   Compiling cc v1.4.7
   Compiling pkg-config v0.3.34
   Compiling version_check v0.9.5
   Compiling generic-array v0.14.7
    Checking hashbrown v0.17.1
    Checking typenum v1.20.1
   Compiling thiserror-impl v2.0.20
   Compiling heapless v0.8.0
    Checking hash32 v0.3.1
    Checking faster-hex v0.10.0
    Checking cpufeatures v0.2.17
   Compiling serde v1.0.229
    Checking block-buffer v0.10.4
    Checking crypto-common v0.1.7
   Compiling serde_derive v1.0.229
    Checking digest v0.10.7
    Checking sha1 v0.10.7
    Checking pin-project-lite v0.2.17
    Checking jiff-core v0.1.1
    Checking sha1-checked v0.10.0
    Checking gix-path v0.12.6
   Compiling synstructure v0.14.0
    Checking gix-features v0.48.1
   Compiling rustix v1.1.5
    Checking jiff v0.2.37
    Checking gix-hash v0.25.1
    Checking linux-raw-sys v0.12.1
   Compiling zerofrom-derive v0.1.8
    Checking zerofrom v0.1.8
   Compiling yoke-derive v0.8.3
    Checking gix-date v0.15.6
   Compiling autocfg v1.5.1
    Checking gix-actor v0.41.2
    Checking gix-hashtable v0.15.2
    Checking futures-core v0.3.34
    Checking yoke v0.8.3
    Checking gix-object v0.61.0
   Compiling zerovec-derive v0.11.6
    Checking errno v0.3.14
    Checking signal-hook-registry v1.4.8
   Compiling tokio-macros v2.7.2
   Compiling displaydoc v0.2.7
    Checking memmap2 v0.9.11
    Checking socket2 v0.6.5
    Checking mio v1.2.3
   Compiling cmake v0.1.58
   Compiling rustversion v1.0.23
    Checking tokio v1.53.1
    Checking zerovec v0.11.8
   Compiling fs_extra v1.3.0
   Compiling dunce v1.0.5
    Checking futures-sink v0.3.34
   Compiling aws-lc-sys v0.45.0
    Checking tinystr v0.8.4
    Checking tempfile v3.27.0
    Checking gix-fs v0.21.2
    Checking gix-chunk v0.7.3
    Checking percent-encoding v2.3.2
   Compiling vcpkg v0.2.15
    Checking writeable v0.6.4
    Checking litemap v0.8.3
    Checking icu_locale_core v2.3.0
   Compiling libz-sys v1.1.29
    Checking gix-tempfile v23.0.2
    Checking potential_utf v0.1.6
    Checking zerotrie v0.2.5
    Checking gix-quote v0.7.2
    Checking tracing-core v0.1.36
   Compiling icu_properties_data v2.3.0
    Checking simd-adler32 v0.3.10
   Compiling icu_normalizer_data v2.3.0
    Checking nonempty v0.12.0
    Checking utf8_iter v1.0.4
    Checking slab v0.4.12
   Compiling aws-lc-rs v1.18.1
    Checking zeroize v1.9.0
    Checking icu_collections v2.3.0
    Checking tracing v0.1.44
    Checking icu_provider v2.3.1
    Checking http v1.5.0
    Checking fnv v1.0.7
    Checking adler2 v2.0.1
    Checking miniz_oxide v0.9.1
    Checking rustls-pki-types v1.15.1
    Checking gix-commitgraph v0.37.1
    Checking gix-glob v0.26.1
    Checking indexmap v2.14.2
    Checking futures-task v0.3.34
    Checking log v0.4.34
    Checking futures-io v0.3.34
    Checking gix-revwalk v0.32.0
    Checking futures-util v0.3.34
    Checking icu_properties v2.3.0
    Checking http-body v1.1.0
    Checking icu_normalizer v2.3.0
    Checking flate2 v1.1.10
    Checking tokio-util v0.7.19
    Checking gix-lock v23.0.1
   Compiling num-traits v0.2.19
   Compiling syn v2.0.119
   Compiling httparse v1.10.1
   Compiling rustls v0.23.45
   Compiling zmij v1.0.23
    Checking untrusted v0.9.0
    Checking idna_adapter v1.2.2
   Compiling encoding_rs v0.8.41
    Checking futures-channel v0.3.34
    Checking try-lock v0.2.5
    Checking tower-layer v0.3.3
    Checking tower-service v0.3.3
    Checking subtle v2.6.1
    Checking atomic-waker v1.1.2
    Checking static_assertions v1.1.0
    Checking unicode-bom v2.0.3
    Checking h2 v0.4.19
    Checking want v0.3.1
    Checking idna v1.1.0
    Checking form_urlencoded v1.2.2
    Checking gix-config-value v0.18.1
    Checking simdutf8 v0.1.5
    Checking compression-core v0.4.33
   Compiling multiversion_no_op v1.0.0
    Checking core_detect v1.0.0
   Compiling serde_json v1.0.151
    Checking shell-words v1.1.1
    Checking gix-command v0.9.2
    Checking hyper v1.11.1
    Checking compression-codecs v0.4.43
    Checking url v2.5.8
    Checking kstring v2.0.4
    Checking sync_wrapper v1.0.2
    Checking gix-sec v0.14.2
    Checking ipnet v2.12.2
    Checking openssl-probe v0.2.1
    Checking base64 v0.22.1
    Checking rustls-native-certs v0.8.4
    Checking gix-attributes v0.33.2
    Checking tower v0.5.3
    Checking hyper-util v0.1.20
    Checking async-compression v0.4.48
    Checking http-body-util v0.1.5
   Compiling heck v0.5.0
   Compiling crossbeam-epoch v0.9.21
    Checking tower-http v0.6.11
    Checking gix-ref v0.64.0
    Checking gix-url v0.36.2
    Checking gix-packetline v0.21.5
    Checking filetime v0.2.29
    Checking mime v0.3.17
   Compiling crossbeam-deque v0.8.8
    Checking either v1.18.0
    Checking base64 v0.23.1
    Checking gix-prompt v0.15.1
    Checking gix-traverse v0.58.0
    Checking gix-revision v0.46.0
   Compiling zstd-sys v2.1.0+zstd.1.5.7
    Checking gix-bitmap v0.3.3
    Checking hashbrown v0.16.1
    Checking fast_time v0.1.32
   Compiling camino v1.2.6
   Compiling rayon-core v1.13.0
    Checking arrayvec v0.7.8
    Checking nm_impl v0.1.50
    Checking uluru v3.1.0
    Checking gix-index v0.52.0
    Checking gix-refspec v0.42.0
    Checking clru v0.6.3
    Checking gix-credentials v0.38.2
    Checking gix-ignore v0.21.1
   Compiling memoffset v0.9.1
    Checking semver v1.0.28
   Compiling cfg_aliases v0.2.2
   Compiling thiserror v1.0.69
    Checking winnow v1.0.4
    Checking utf8parse v0.2.2
    Checking anstyle-parse v1.0.0
    Checking toml_parser v1.1.3+spec-1.1.0
   Compiling nix v0.30.1
    Checking gix-worktree v0.53.0
    Checking gix-pack v0.71.0
    Checking nm v0.1.50
    Checking gix-config v0.57.0
    Checking gix-filter v0.31.0
    Checking gix-pathspec v0.18.1
   Compiling maybe-async v0.2.11
   Compiling thiserror-impl v1.0.69
    Checking gix-shallow v0.12.1
    Checking gix-negotiate v0.32.0
    Checking arc-swap v1.9.2
   Compiling fs-err v3.3.1
    Checking serde_spanned v1.1.1
    Checking smol_str v0.3.6
    Checking toml_datetime v1.1.1+spec-1.1.0
    Checking io-close v0.3.7
   Compiling zstd-safe v8.0.0
   Compiling typeid v1.0.3
    Checking bit-vec v0.8.0
    Checking is_terminal_polyfill v1.70.2
    Checking anstyle-query v1.1.5
   Compiling portable-atomic v1.15.0
    Checking bumpalo v3.20.3
    Checking unicode-properties v0.1.4
    Checking colorchoice v1.0.5
    Checking toml_writer v1.1.2+spec-1.1.0
    Checking anstyle v1.0.14
    Checking toml v1.1.6+spec-1.1.0
    Checking anstream v1.0.0
    Checking ra-ap-rustc_lexer v0.160.0
error[E0080]: evaluation panicked: unicode-properties and unicode-ident must use the same Unicode version, `unicode_properties::UNICODE_VERSION` and `unicode_ident::UNICODE_VERSION` are different.
  --> /home/runner/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/ra-ap-rustc_lexer-0.160.0/src/lib.rs:47:9
   |
47 | /         panic!(
48 | |             "unicode-properties and unicode-ident must use the same Unicode version, \
49 | |             `unicode_properties::UNICODE_VERSION` and `unicode_ident::UNICODE_VERSION` are \
50 | |             different."
51 | |         );
   | |_________^ evaluation of `_` failed here

For more information about this error, try `rustc --explain E0080`.
error: could not compile `ra-ap-rustc_lexer` (lib) due to 1 previous error
warning: build failed, waiting for other jobs to finish...

-----

error: failed to build rustdoc for crate cargo-aprz-lib v1.1.2
note: this is usually due to a compilation error in the crate,
      and is unlikely to be a bug in cargo-semver-checks
note: the following command can be used to reproduce the error:
      cargo new --lib example &&
          cd example &&
          echo '[workspace]' >> Cargo.toml &&
          cargo add --path /home/runner/work/ox-tools/ox-tools/crates/cargo-aprz-lib --features internals &&
          cargo check &&
          cargo doc

error: aborting due to failure to build rustdoc for crate cargo-aprz-lib v1.1.2

Move recovered children into a process-wide retry owner so ordinary and contained cleanup paths remain bounded without dropping the last wait handle.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: a9fc919b-99f7-4134-aad1-2116321b4e0c
Copilot AI review requested due to automatic review settings September 18, 2026 13:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

A reaper unwind race can strand children in the retry queue, and the new pipe backend lacks corresponding unsafe-crate design documentation.

Get a fresh assessment by requesting another Copilot review.

Review details
  • Files reviewed: 21/23 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment on lines +224 to +228
let mut reaper = CHILD_REAPER.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
let mut active = std::mem::take(&mut reaper.children);
reaper.retry_children.append(&mut active);
reaper.running = false;
CHILD_REAPER_READY.notify_all();
pub mod interrupt;
#[cfg(windows)]
pub mod job;
pub mod pipe;
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.

5 participants