How Worktrunk Command Execution Differs from `Command::output()`

Worktrunk replaces the standard std::process::Command::output() method with a custom shell_exec::Cmd wrapper that adds structured tracing, automatic Git environment sanitization, streaming output support, and signal forwarding to every subprocess invocation.

The max-sixty/worktrunk repository implements a Rust CLI for managing git worktrees. Rather than invoking Command::output() directly, every external command flows through the shell_exec::Cmd abstraction defined in src/shell_exec.rs. This design centralizes cross-cutting concerns like logging, environment hygiene, and process lifecycle management that raw std::process::Command leaves to individual call sites.

The shell_exec::Cmd Abstraction

Worktrunk's command execution centers on the Cmd struct, which wraps std::process::Command with domain-specific behavior for git operations. While Command::output() returns raw bytes that the caller must parse and log manually, Cmd provides methods like run(), stream(), and pipe_into() that handle output modes, error conversion, and observability automatically.

Structured Tracing with CommandTrace

Every command spawned through Cmd emits a [wt-trace] record via the CommandTrace enum. In src/trace/emit.rs, the implementation calls CommandTrace::complete or CommandTrace::fail to log the execution result, worktree context, and exit status. This eliminates the need for manual println! statements at each call site and ensures uniform observability across the codebase.

Git Environment Sanitization

When executing git commands inside worktrees, inherited environment variables like GIT_DIR or GIT_WORK_TREE can accidentally hijack repository discovery. The Cmd::scrub_git_discovery_env() method removes these variables before spawning the child process, ensuring the command operates on the intended repository. This safety check is applied consistently in files like src/commands/worktree/add.rs.

Streaming vs. Buffered Output

Unlike Command::output(), which buffers the entire stdout and stderr before returning, Cmd offers stream() for line-by-line output inheritance. This keeps the CLI responsive during long-running operations like git fetch, whereas output() would block until completion and consume memory proportional to the output size.

Signal Propagation and Error Handling

Cmd handles process signals explicitly. When a user sends SIGINT or SIGTERM, Worktrunk forwards these signals to the child process group rather than orphaning the process. The wrapper also converts exit codes into anyhow::Result types and exposes precise signal information through helpers like interrupt_signal(), whereas Command::output() requires manual inspection of ExitStatus.

Code Examples in Practice

Capturing output with automatic tracing and environment scrubbing:

// From src/commands/worktree/add.rs
Cmd::new("git")
    .args(["worktree", "add", "-b", &branch, &path])
    .current_dir(&repo_root)
    .context(&branch)               // adds worktree context to traces
    .scrub_git_discovery_env()      // prevents GIT_* env leakage
    .run()?;                        // returns anyhow::Result<String>

Streaming output for real-time user feedback:

// Pattern from src/commands/worktree/remove.rs
Cmd::new("git")
    .args(["worktree", "remove", &worktree_path])
    .current_dir(&repo_root)
    .context(&branch)
    .scrub_git_discovery_env()
    .stream()?;        // inherits stdio line-by-line

Handling signals in long-running operations:

// SIGINT is forwarded to the git process automatically
Cmd::new("git")
    .args(["fetch", "--all"])
    .current_dir(&repo_root)
    .context(&branch)
    .scrub_git_discovery_env()
    .run()?;

Summary

  • Worktrunk uses shell_exec::Cmd instead of raw Command::output() to centralize command execution logic in src/shell_exec.rs.
  • The CommandTrace infrastructure in src/trace/emit.rs provides structured logging for every spawn via complete and fail events.
  • scrub_git_discovery_env() prevents accidental git repository hijacking by sanitizing GIT_DIR and GIT_WORK_TREE variables.
  • stream() enables responsive CLI output during long operations, unlike the buffering behavior of output().
  • Signal forwarding and anyhow::Result conversion provide robust error handling that Command::output() lacks.

Frequently Asked Questions

Why doesn't Worktrunk use Command::output() directly?

Worktrunk requires consistent tracing, environment sanitization, and signal handling across dozens of git invocations. Reimplementing these checks at every Command::output() call site would risk inconsistencies and bugs. The Cmd wrapper enforces these policies uniformly according to the source code in src/shell_exec.rs.

How does Cmd::stream() differ from Command::stdout(Stdio::inherit())?

While Command allows inheriting stdout, Cmd::stream() combines inheritance with structured tracing, error handling, and the context() worktree identifier. It also integrates with Worktrunk's signal forwarding system, which raw Command configurations do not provide automatically.

What happens to git environment variables when running commands?

Cmd::scrub_git_discovery_env() removes GIT_DIR and GIT_WORK_TREE from the child's environment before execution. This ensures that commands running inside a Worktrunk-managed worktree do not accidentally reference the parent repository or other worktrees, as implemented in src/commands/worktree/add.rs.

Does Cmd support piping output between commands?

Yes. The pipe_into() method allows chaining commands where the output of one process feeds into another, while maintaining the same tracing and error handling guarantees provided by run() and stream(). This is part of the consistent API that shell_exec::Cmd provides over raw Command usage.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →