gitbutlerapp/gitbutler · error

Invalid UTF-8 in ANTHROPIC_API_KEY

Error message

Invalid UTF-8 in ANTHROPIC_API_KEY

What it means

Thrown by `anthropic_env_var_creds` when `ANTHROPIC_API_KEY` exists in the environment but is not valid UTF-8. The function uses `OsString::into_string()`, which fails for non-UTF-8 bytes (e.g. on Unix, arbitrary byte sequences are legal in env vars), and maps that failure to this error. The provider refuses to proceed with a malformed credential rather than sending garbage to the API.

Solutions

  1. Re-export the variable with a clean ASCII/UTF-8 value: export ANTHROPIC_API_KEY=$(cat key.txt) after verifying key.txt is UTF-8
  2. Inspect the raw bytes (e.g. `echo -n "$ANTHROPIC_API_KEY" | xxd`) and remove non-ASCII characters
  3. Re-copy the key fresh from the Anthropic console and avoid rich-text paste
  4. Alternatively store the key via GitButler settings instead of the environment

Example fix

// before (shell, contains invalid byte)
export ANTHROPIC_API_KEY=$'sk-ant-\xff...'

// after
export ANTHROPIC_API_KEY=sk-ant-api03-clean-utf8-key
Defensive patterns

Strategy: validation

Validate before calling

fn anthropic_key_is_utf8() -> bool {
    std::env::var_os("ANTHROPIC_API_KEY")
        .map(|v| v.into_string().is_ok())
        .unwrap_or(false)
}

Try / catch

if let Err(e) = creds_result {
    if e.to_string().contains("Invalid UTF-8") {
        eprintln!("ANTHROPIC_API_KEY contains non-UTF-8 bytes; re-copy the key");
    }
}

Prevention

When it happens

Trigger: `std::env::var_os("ANTHROPIC_API_KEY")` succeeds but the returned OsString fails `into_string()` — the variable contains invalid UTF-8 bytes, typically from a corrupted shell profile, a bad export, or binary paste into a config file.

Common situations: Pasting a key with a broken multi-byte character from a rich-text editor; shell profile encoding issues; a script writing the key with wrong encoding; secrets managers emitting raw bytes.

Understand the failure class

Background: "is not a valid" / "Invalid ... value" environment variable errors: how libraries validate env vars and what to do when they reject yours — this error's family across 48 libraries.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18). Data as JSON: /api/errors/48723306d5a19f84. Report an issue: GitHub.

Appendix: source

Thrown at crates/but-llm/src/anthropic.rs:159

        Ok((CredentialsKind::GitButlerProxied, creds))
    }

    fn anthropic_own_key_creds() -> Result<(CredentialsKind, Sensitive<String>)> {
        let creds = secret::retrieve(AI_ANTHROPIC_SECRET_HANDLE, secret::Namespace::Global)?
            .ok_or(anyhow::anyhow!(
                "No Anthropic own key configured. Add this through the GitButler settings"
            ))?;
        Ok((CredentialsKind::OwnAnthropicKey, creds))
    }

    fn anthropic_env_var_creds() -> Result<(CredentialsKind, Sensitive<String>)> {
        let creds = Sensitive(
            std::env::var_os("ANTHROPIC_API_KEY")
                .ok_or(anyhow::anyhow!(
                    "Environment variable ANTHROPIC_API_KEY is not set"
                ))?
                .into_string()
                .map_err(|_| anyhow::anyhow!("Invalid UTF-8 in ANTHROPIC_API_KEY"))?,
        );
        Ok((CredentialsKind::EnvVarAnthropicKey, creds))
    }
}

impl LLMClient for AnthropicProvider {
    fn model(&self) -> Option<String> {
        self.model.clone()
    }

    fn tool_calling_loop_stream(
        &self,
        system_message: &str,
        chat_messages: Vec<ChatMessage>,
        tool_set: &mut impl Toolset,
        model: &str,
        on_token: impl Fn(&str) + Send + Sync + 'static,
    ) -> Result<(String, Vec<ChatMessage>)> {

View on GitHub (pinned to 58e5313667)