Hmbown/CodeWhale · error · std::io::Error
gh not found
Error message
gh not found
What it means
`upload_gist` shares a file as a GitHub Gist by shelling out to the `gh` CLI. When the `gh` executable cannot be located on the system, the spawned command construction fails with `NotFound` and this "gh not found" error string is propagated to the share flow.
Solutions
- Install the GitHub CLI: e.g. `brew install gh`, `apt install gh`, `winget install GitHub.cli`, or see https://cli.github.com.
- Verify installation with `gh --version` in the same shell/environment codewhale runs in (PATH must contain it).
- If `gh` is installed via a non-default prefix, add its directory to PATH (e.g. `export PATH="$HOME/.local/bin:$PATH"`).
- Authenticate once installed: `gh auth login` — the share step also needs a valid GitHub session.
Example fix
// before: error at share time
let output = tokio::task::spawn_blocking(move || {
let mut cmd = crate::dependencies::Gh::command()
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, "gh not found"))?;
// after (user fix, terminal)
$ brew install gh && gh auth login
$ codewhale # share again Defensive patterns
Strategy: fallback
Validate before calling
// caller pre-check before invoking share
fn gh_available() -> bool {
std::process::Command::new("gh").arg("--version").output()
.map(|o| o.status.success()).unwrap_or(false)
}
if !gh_available() { eprintln!("Install the GitHub CLI (gh) first."); } Type guard
fn gh_binary_present() -> bool {
std::env::var_os("PATH")
.map(|p| std::env::split_paths(&p).any(|d| {
#[cfg(unix)] { d.join("gh").is_file() }
#[cfg(windows)] { d.join("gh.exe").is_file() }
}))
.unwrap_or(false)
} Try / catch
match perform_share(path).await {
Err(msg) if msg.contains("gh not found") => {
eprintln!("gh CLI missing. Install it (https://cli.github.com), run `gh auth login`, then retry.");
}
Err(msg) => eprintln!("share failed: {msg}"),
Ok(url) => println!("shared: {url}"),
} Prevention
- Install gh and keep it on PATH in every environment that runs codewhale (shell, CI, containers).
- Run `gh auth login` once per machine; expired sessions surface separately.
- After installing version-manager-managed tools, refresh PATH in the same shell.
- Add a doctor/startup check that verifies required external binaries.
When it happens
Trigger: Invoking the project share command (`perform_share` -> `upload_gist`) on a machine where `Gh::command()` cannot find the `gh` binary in PATH.
Common situations: GitHub CLI not installed at all; `gh` installed via a version manager whose PATH is not active in the environment running codewhale; running inside a container or service without the CLI.
Related errors
- A positive pull request number is required
- gh api POST repos/ /pulls/ /reviews failed
- `gh` CLI not found on PATH. Install GitHub CLI…
- `gh` CLI not found on PATH. Install GitHub CLI…
- gh did not report a pull request URL; refusing to invent…
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/51f3c67fda436881.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/commands/groups/project/share.rs:163
}
/// Write HTML to a secure temp file and keep it alive for upload.
fn write_temp_html(html: &str) -> Result<tempfile::NamedTempFile, String> {
let mut tmp = tempfile::Builder::new()
.prefix("codewhale-share-")
.suffix(".html")
.tempfile()
.map_err(|e| format!("{e}"))?;
tmp.write_all(html.as_bytes()).map_err(|e| format!("{e}"))?;
Ok(tmp)
}
/// Upload a file as a GitHub Gist using the `gh` CLI.
async fn upload_gist(path: &Path) -> Result<String, String> {
let path_owned = path.to_path_buf();
let output = tokio::task::spawn_blocking(move || {
let mut cmd = crate::dependencies::Gh::command()
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, "gh not found"))?;
cmd.args([
"gist",
"create",
"--public",
path_owned.to_string_lossy().as_ref(),
"--filename",
"session-export.html",
"--desc",
"codewhale Session Export",
])
.output()
})
.await
.map_err(|join_err| format!("gh gist create panicked: {join_err}"))?
.map_err(|e| format!("Failed to run `gh gist create`: {e}"))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);View on GitHub (pinned to 73e0f67d83)