{"record":{"id":"06e7567734bf58f0","repo":"can1357/oh-my-pi","slug":"cli-message-command-exit-code-stdout-stderr","errorCode":null,"errorMessage":"cli_message(command, exit_code, stdout, stderr)","messagePattern":"cli_message\\(command, exit_code, stdout, stderr\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"crates/pi-vcs/src/error.rs","lineNumber":64,"sourceCode":"\n\t/// A merge-style operation (cherry-pick, stash pop, 3-way apply) hit\n\t/// conflicting changes.\n\t#[error(\"merge conflict in {} file(s)\", paths.len())]\n\tConflict {\n\t\t/// Worktree-relative paths left in a conflicted state.\n\t\tpaths: Vec<String>,\n\t},\n\n\t/// A patch did not apply (context mismatch, missing file, malformed input).\n\t#[error(\"patch does not apply: {message}\")]\n\tPatchFailed {\n\t\t/// Human-readable reason, including the offending path when known.\n\t\tmessage: String,\n\t},\n\n\t/// A CLI-backed operation (push/fetch/clone, reftable fallback) exited\n\t/// non-zero. Carries the captured streams for user-facing error surfaces.\n\t#[error(\"{}\", crate::error::cli_message(command, *exit_code, stdout, stderr))]\n\tCli {\n\t\t/// Rendered command line (`git push --no-follow-tags …`).\n\t\tcommand:   String,\n\t\t/// Process exit code.\n\t\texit_code: i32,\n\t\t/// Captured stdout (may be truncated).\n\t\tstdout:    String,\n\t\t/// Captured stderr (may be truncated).\n\t\tstderr:    String,\n\t},\n\n\t/// A CLI-backed operation exceeded its deadline and was killed.\n\t#[error(\"timed out: {command}\")]\n\tCliTimeout {\n\t\t/// Rendered command line.\n\t\tcommand: String,\n\t},\n","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/crates/pi-vcs/src/error.rs#L46-L82","documentation":"Error::Cli is raised when a CLI-backed operation (push/fetch/clone, reftable fallback) exits non-zero. It carries the rendered command line, exit code, and captured stdout/stderr (possibly truncated) so user-facing error surfaces can show exactly what git/jj printed.","triggerScenarios":"Any CLI-spawned git/jj command failing: `git push` rejected (non-fast-forward, no upstream, auth failure), `git fetch` network/auth errors, `git clone` of a nonexistent URL, or the reftable fallback path failing against an old git binary.","commonSituations":"Expired/missing credentials (HTTPS token revoked, SSH agent not loaded), branch protected on the remote, remote behind local history (need pull/rebase), offline/VPN-down, or git too old to support a flag the library passes.","solutions":["Read `stderr` in the error — git's own message usually names the exact problem (auth, rejected non-fast-forward, unknown option).","For push rejections: `git pull --rebase` (fetch and integrate remote state), then push again.","For auth failures: refresh credentials (`gh auth login`, ssh-add, credential manager) and retry.","For unknown-option failures, upgrade the installed git/jj binary to a version supporting the flags used."],"exampleFix":"// before\nrepo.push(\"origin\", \"main\")?; // opaque failure\n// after\nif let Err(Error::Cli { command, exit_code, stderr, .. }) = repo.push(\"origin\", \"main\") {\n    eprintln!(\"{command} exited {exit_code}:\\n{stderr}\");\n    if stderr.contains(\"non-fast-forward\") { repo.pull_rebase(\"origin\", \"main\")?; repo.push(\"origin\", \"main\")?; }\n}","handlingStrategy":"try-catch","validationCode":"fn remote_reachable(url: &str) -> bool {\n    std::process::Command::new(\"git\").args([\"ls-remote\", \"--exit-code\", url, \"HEAD\"])\n        .status().map(|s| s.success()).unwrap_or(false)\n}\nif !remote_reachable(&remote_url) { eprintln!(\"cannot reach {remote_url} — check network/auth\"); return Ok(()); }","typeGuard":"fn is_cli_error(err: &pi_vcs::Error) -> Option<(i32, &str)> {\n    match err {\n        pi_vcs::Error::Cli { exit_code, stderr, .. } => Some((*exit_code, stderr)),\n        _ => None,\n    }\n}","tryCatchPattern":"match repo.push(\"origin\", branch) {\n    Err(pi_vcs::Error::Cli { exit_code, stderr, .. }) => {\n        if stderr.contains(\"non-fast-forward\") {\n            repo.pull_rebase(\"origin\", branch)?;\n            repo.push(\"origin\", branch)?;\n        } else {\n            anyhow::bail!(\"push failed (exit {exit_code}):\\n{stderr}\");\n        }\n    }\n    other => other?,\n}","preventionTips":["Keep credentials fresh (ssh-agent loaded, credential helper configured) before automated push/fetch.","Pull/rebase before pushing to reduce non-fast-forward rejections.","Pin a recent git/jj version in CI so library-passed flags are always supported.","Always surface stderr from this variant in user-facing error messages — git's text names the cause."],"tags":["vcs","git","cli","process-exit","push"],"backgroundTag":"git-cli-nonzero-exit","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}