Kuberwastaken/claurst · error

stdin was requested as piped

Error message

stdin was requested as piped

What it means

This panic asserts that `git apply`'s stdin handle is available after spawning the child with `.stdin(Stdio::piped())`. Because piped stdin was explicitly requested, `child.stdin` is guaranteed to be `Some`, so the expect documents an invariant rather than handling a real failure. It can only fire if the spawn configuration was changed to not pipe stdin.

Solutions

  1. Confirm the git Command still sets `.stdin(Stdio::piped())` before `.spawn()` — that is the invariant this expect guards.
  2. If stdin configuration became dynamic, replace the expect with `ok_or`/`map_err` returning `MoveError::Apply("stdin not piped".into())`.
  3. Keep the pipe setup and the `stdin.take()` adjacent so future edits cannot silently decouple them.
  4. Add a unit test that runs apply_changes against a temp git repo so a removed pipe fails the test instead of panicking in production.

Example fix

// before
let mut stdin = child.stdin.take().expect("stdin was requested as piped");
// after
let mut stdin = child
    .stdin
    .take()
    .ok_or_else(|| MoveError::Apply("git apply stdin was not piped".into()))?;
Defensive patterns

Strategy: validation

Validate before calling

// Assert the spawn configuration before relying on stdin:
let mut cmd = std::process::Command::new("git");
cmd.args(["apply", "--whitespace=fix"])
    .stdin(std::process::Stdio::piped())
    .stdout(std::process::Stdio::piped())
    .stderr(std::process::Stdio::piped());
debug_assert!(true, "stdin must remain piped for git apply");

Try / catch

// Panic, not Result — wrap apply_changes:
let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| apply_changes(&repo, &patch)));
if res.is_err() { /* report MoveError::Apply("internal: stdin not piped") */ }

Prevention

When it happens

Trigger: Calling `apply_changes` (via `move_session_changes`) when the preceding `Command::new("git").arg("apply")...spawn()` was configured without `.stdin(Stdio::piped())`, making `child.stdin.take()` return None.

Common situations: A refactor of the git Command builder that removed or conditionally skipped `.stdin(Stdio::piped())`; someone switching stdin to `null()` or `inherit()`; copy-pasting the spawn block into another function without the pipe.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10). Data as JSON: /api/errors/11e188caaefbe694. Report an issue: GitHub.

Appendix: source

Thrown at src-rust/crates/commands/src/new_move.rs:339

}

/// Apply a captured patch to the destination worktree via `git apply -`.
fn apply_changes(dest_root: &Path, patch: &str) -> Result<(), MoveError> {
    use std::io::Write;
    use std::process::Stdio;

    let mut child = Command::new("git")
        .current_dir(dest_root)
        .args(["apply", "-"])
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .map_err(|e| MoveError::Apply(e.to_string()))?;

    // Stream the patch on a separate thread so a large patch can't deadlock
    // against git filling its stdout/stderr pipes.
    let mut stdin = child.stdin.take().expect("stdin was requested as piped");
    let patch_owned = patch.to_string();
    let writer = std::thread::spawn(move || {
        let _ = stdin.write_all(patch_owned.as_bytes());
        // `stdin` drops here, closing the pipe so git sees EOF.
    });

    let output = child
        .wait_with_output()
        .map_err(|e| MoveError::Apply(e.to_string()))?;
    let _ = writer.join();

    if output.status.success() {
        Ok(())
    } else {
        Err(MoveError::Apply(git_stderr(&output, "git apply failed")))
    }
}

View on GitHub (pinned to b0637c97ec)