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 in crates/but-llm/src/anthropic.rs when ANTHROPIC_API_KEY is present but its bytes are not valid UTF-8 (var_os succeeded, into_string failed). Keys are transmitted as strings, so a non-UTF-8 value — typically a mangled shell encoding or a binary-pasted value — is rejected before use.

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 caf1f223d3)

Solutions

  1. Re-set the variable with a clean ASCII key: export ANTHROPIC_API_KEY="sk-ant-...".
  2. Check for stray characters: print | xxd | head to inspect the bytes actually stored.
  3. Set the variable from a file with known UTF-8/ASCII content (e.g. export ANTHROPIC_API_KEY="$(cat keyfile)").
Defensive patterns

Strategy: validation

Validate before calling

// Validate the key is clean UTF-8 before use
match std::env::var("ANTHROPIC_API_KEY") {
    Ok(k) if !k.is_empty() => { /* proceed */ },
    Ok(_) => eprintln!("ANTHROPIC_API_KEY is empty"),
    Err(std::env::VarError::NotUnicode(_)) => eprintln!("ANTHROPIC_API_KEY is not valid UTF-8; re-export it"),
    Err(std::env::VarError::NotPresent) => eprintln!("ANTHROPIC_API_KEY not set"),
}

Type guard

fn anthropic_env_key_is_valid() -> bool {
    matches!(std::env::var("ANTHROPIC_API_KEY"), Ok(k) if !k.is_empty() && k.is_ascii())
}

Prevention

When it happens

Trigger: ANTHROPIC_API_KEY containing raw non-UTF-8 bytes (e.g. a Latin-1 or truncated multi-byte sequence); the variable set from a script that wrote binary data; locale/encoding corruption in the environment.

Common situations: Keys pasted into terminals with mismatched encodings; values assembled from command substitution that captured stray bytes; minimal container images with broken locale settings mangling exported strings.

Understand the failure class

Related errors


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