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
- Re-set the variable with a clean ASCII key: export ANTHROPIC_API_KEY="sk-ant-...".
- Check for stray characters: print | xxd | head to inspect the bytes actually stored.
- 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
- Store keys in single-quoted exports to avoid shell interpolation or encoding surprises.
- In CI, inject secrets via the runner's secret mechanism rather than echoed shell lines.
- Treat non-ASCII API keys as configuration bugs — provider keys are ASCII by construction.
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
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Invalid UTF-8 in OPENAI_API_KEY
- Environment variable ANTHROPIC_API_KEY is not set
- PR template exists but must be valid UTF-8 text or markdown
- No GitButler token available. Log-in to use the GitButler An
- No Anthropic own key configured. Add this through the GitBut
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/48723306d5a19f84.
Report an issue: GitHub.