xai-org/grok-build · error

pbcopy exited with status

Error message

pbcopy exited with status {status}

What it means

On the macOS pbcopy write leg, if the spawned pbcopy process finishes with a non-zero exit status after the 2-second deadline wait, the leg reports failure with this message. It distinguishes 'pbcopy ran but failed' from spawn failures or timeouts.

Solutions

  1. Check whether the environment has access to the macOS user pasteboard (run `echo hi | pbcopy && pbpaste` manually)
  2. Run inside a regular graphical/login session rather than an SSH or restricted context
  3. Rely on the arboard leg, which set_text attempts alongside pbcopy, or fix the session so it works
  4. Inspect the reported status code for the specific pbcopy failure
Defensive patterns

Strategy: try-catch

Validate before calling

// Confirm pbcopy works in the current session
let ok = std::process::Command::new("sh")
    .arg("-c").arg("echo test | pbcopy && [ \"$(pbpaste)\" = test ]")
    .status().map(|s| s.success()).unwrap_or(false);

Try / catch

match clipboard::set_text(&text) {
    Err(e) if e.to_string().contains("pbcopy exited with status") => {
        eprintln!("pbcopy failed ({e}); trying alternate backend");
        // fall back to arboard-based path or manual instructions
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling set_text on macOS when pbcopy starts but exits non-zero — typically because the pasteboard is unavailable in the session (SSH without a proper launchd user session, sandbox restrictions).

Common situations: SSH sessions on macOS where the launchd context lacks a clipboard; automation tools running pbcopy under restricted contexts; full pasteboard daemon issues.

Related errors


AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31). Data as JSON: /api/errors/8060971c205cd446. Report an issue: GitHub.

Appendix: source

Thrown at crates/codegen/xai-grok-shared/src/clipboard.rs:1018

            ..Default::default()
        };
        let result = (|| -> anyhow::Result<()> {
            // Spooled stdin (not a pipe): a stalled pbcopy must not block the
            // UI thread on the write, and the deadline wait needs stdin closed.
            let stdin = super::spool_for_stdin(text.as_bytes())?;
            let mut cmd = Command::new("pbcopy");
            cmd.stdin(Stdio::from(stdin))
                .stdout(Stdio::null())
                .stderr(Stdio::null());
            xai_grok_tools::util::detach_std_command(&mut cmd);
            #[allow(clippy::disallowed_methods)] // short-lived clipboard helper, waited on below
            let mut child = cmd
                .spawn()
                .map_err(|e| anyhow::anyhow!("failed to spawn pbcopy: {e}"))?;
            let deadline = std::time::Duration::from_secs(2);
            let status = super::wait_with_deadline(&mut child, deadline)?;
            if !status.success() {
                anyhow::bail!("pbcopy exited with status {status}");
            }
            Ok(())
        })();
        match result {
            Ok(()) => {
                outcome.cli_ok = true;
                outcome.cli_ok_tools.push("pbcopy");
                outcome.any_ok = true;
            }
            Err(e) => tracing::debug!("pbcopy failed: {e}"),
        }
        outcome
    }

    /// Read an image from the macOS clipboard via `osascript`.
    ///
    /// Probes PNG, TIFF, then JPEG in a single `osascript` invocation
    /// using nested `try` blocks. This avoids spawning up to 3 separate

View on GitHub (pinned to bc7f02eddd)