gitbutlerapp/gitbutler · info

configured

Error message

configured

What it means

run_pre_push() spawns the pre-push hook with .stdin(Stdio::piped()) (hooks.rs:270) and later grabs child.stdin with expect("configured"). std::process::Child::stdin is Some only when the command was configured for piped stdin; since the spawn setup does exactly that a few lines above, None is impossible unless the construction is refactored. The code below already tolerates the hook exiting early (BrokenPipe is ignored).

Source

Thrown at crates/gitbutler-repo/src/hooks.rs:283

            prep.command = gix::path::from_bstring(with_slashes_for_bash.into_owned()).into();
        }
        prep.arg(remote_name).arg(remote_url)
    })
    .current_dir(repo.workdir().unwrap_or(repo.git_dir()))
    .stdin(Stdio::piped())
    .spawn()?;

    {
        let remote_commit = repo
            .try_find_reference(&remote_tracking_branch.to_string())?
            .map(|mut reference| reference.peel_to_id().map(|id| id.detach()))
            .transpose()?
            .unwrap_or_else(|| repo.object_hash().null());
        // THIS IS WRONG: but is correct in the common case. This also is an issue when the ref is actually pushed,
        // but we can fix it when moving everything to `gix`.
        let local_tracking_branch_deduced =
            format!("refs/heads/{}", remote_tracking_branch.branch());
        let stdin = child.stdin.as_mut().expect("configured");
        let refspec = format!(
            "{local_tracking_branch_deduced} {local_commit} {remote_tracking_branch} {remote_commit}\n"
        );
        // Hooks may exit before reading stdin if they don't need the refspec info.
        // The actual success/failure is determined by the exit code via wait_with_output() below.
        if let Err(err) = stdin.write_all(refspec.as_bytes())
            && err.kind() != std::io::ErrorKind::BrokenPipe
        {
            return Err(err.into());
        }
    }

    let output = child.wait_with_output()?;
    if output.status.success() {
        Ok(HookResult::Success)
    } else {
        let error = join_output(
            output.stdout.to_str_lossy().into_owned(),

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Keep .stdin(Stdio::piped()) on the Command used to spawn hooks
  2. Replace the expect with an explicit error so future refactors fail loudly with context (see exampleFix)
  3. Keep the existing hook integration tests (crates/gitbutler-repo/tests/repo/hooks.rs) green so spawn regressions surface there

Example fix

// before
let stdin = child.stdin.as_mut().expect("configured");

// after
let Some(stdin) = child.stdin.as_mut() else {
    anyhow::bail!("pre-push hook was spawned without piped stdin");
};
Defensive patterns

Strategy: validation

Validate before calling

// When spawning hook processes, make the stdin contract explicit up front
let mut cmd: std::process::Command = /* ... */;
cmd.stdin(std::process::Stdio::piped()); // required: refspec is written to the hook
assert_eq!(cmd.get_stdin(), Some(&std::process::Stdio::piped()));

Try / catch

// Fail with context instead of panicking if the contract is broken
let Some(stdin) = child.stdin.as_mut() else {
    anyhow::bail!("hook was spawned without piped stdin");
};

Prevention

When it happens

Trigger: Any pre-push hook execution (pushing a repo that has .git/hooks/pre-push or .husky/pre-push). The panic fires only if the .stdin(Stdio::piped()) line is removed or changed in the Command construction above.

Common situations: Refactors of the hook-spawning code that drop or conditionalize piped stdin. Not user-triggerable: a fast-exiting hook is handled by the BrokenPipe check that follows.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/45de0a4a883f1b49. Report an issue: GitHub.