use crate::context::Context;
use crate::provider::claude::paths as claude;
use serde_json::Value;
use std::path::PathBuf;
pub(crate) const OVERRIDING_ENV: [&str; 9] = [
"ANTHROPIC_API_KEY",
"ANTHROPIC_AUTH_TOKEN",
"CLAUDE_CODE_OAUTH_TOKEN",
"CLAUDE_CODE_USE_BEDROCK",
"CLAUDE_CODE_USE_VERTEX",
"CLAUDE_CODE_USE_FOUNDRY",
"CLAUDE_CODE_USE_GATEWAY",
"CLAUDE_CODE_USE_ANTHROPIC_AWS",
"CLAUDE_CODE_USE_ANTHROPIC_GOOGLE_CLOUD",
];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Override {
pub layer: String,
pub key: String,
}
impl std::fmt::Display for Override {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} in {}", self.key, self.layer)
}
}
fn managed_dir() -> PathBuf {
if cfg!(target_os = "macos") {
PathBuf::from("/Library/Application Support/ClaudeCode")
} else {
PathBuf::from("/etc/claude-code")
}
}
fn managed_files() -> Vec<PathBuf> {
let dir = managed_dir();
let mut files = vec![dir.join("managed-settings.json")];
if let Ok(entries) = std::fs::read_dir(dir.join("managed-settings.d")) {
let mut drop_ins: Vec<PathBuf> = entries
.flatten()
.map(|e| e.path())
.filter(|p| p.extension().is_some_and(|e| e == "json"))
.collect();
drop_ins.sort();
files.extend(drop_ins);
}
files
}
fn is_set(value: &str) -> bool {
!matches!(value.trim(), "" | "0" | "false")
}
fn overrides_in(path: &std::path::Path) -> Vec<Override> {
let Ok(raw) = std::fs::read_to_string(path) else {
return Vec::new();
};
let Ok(json) = serde_json::from_str::<Value>(&raw) else {
return Vec::new();
};
let layer = path.display().to_string();
let mut found = Vec::new();
if json
.get("apiKeyHelper")
.and_then(Value::as_str)
.is_some_and(|h| !h.trim().is_empty())
{
found.push(Override {
layer: layer.clone(),
key: "apiKeyHelper".into(),
});
}
if let Some(env) = json.get("env").and_then(Value::as_object) {
for name in OVERRIDING_ENV {
let set = match env.get(name) {
Some(Value::String(v)) => is_set(v),
Some(Value::Bool(b)) => *b,
Some(Value::Number(n)) => n.as_i64() != Some(0),
_ => false,
};
if set {
found.push(Override {
layer: layer.clone(),
key: format!("env.{name}"),
});
}
}
}
found
}
pub fn custom_oauth(ctx: &Context) -> bool {
if ctx.custom_oauth() {
return true;
}
managed_files()
.iter()
.chain(std::iter::once(
&claude::config_dir(ctx).join("settings.json"),
))
.any(|path| {
std::fs::read_to_string(path)
.ok()
.and_then(|raw| serde_json::from_str::<Value>(&raw).ok())
.and_then(|json| {
json.get("env")?
.get("CLAUDE_CODE_CUSTOM_OAUTH_URL")?
.as_str()
.map(is_set)
})
.unwrap_or(false)
})
}
pub fn overrides(ctx: &Context) -> Vec<Override> {
let mut found: Vec<Override> = managed_files()
.iter()
.chain(std::iter::once(
&claude::config_dir(ctx).join("settings.json"),
))
.flat_map(|path| overrides_in(path))
.collect();
found.extend(ctx.overriding_auth().iter().map(|key| Override {
layer: "the environment".into(),
key: key.clone(),
}));
found
}
#[cfg(test)]
pub(crate) mod tests_support {
use super::*;
pub(crate) struct Scratch(pub(crate) PathBuf);
impl Drop for Scratch {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
pub(crate) fn home(name: &str) -> (Context, PathBuf, Scratch) {
let root = std::env::temp_dir().join(format!(
"pitboard-settings-{name}-{}-{:?}",
std::process::id(),
std::thread::current().id()
));
let _ = std::fs::remove_dir_all(&root);
let config = root.join(".claude");
std::fs::create_dir_all(&config).expect("a config dir");
let ctx = Context::new(root.clone())
.with_claude_config_dir(config.to_string_lossy().into_owned());
(ctx, config, Scratch(root))
}
pub(crate) fn write(path: &std::path::Path, value: Value) {
std::fs::write(path, value.to_string()).expect("written");
}
}
#[cfg(test)]
mod tests {
use super::tests_support::*;
use super::*;
use serde_json::json;
#[test]
fn an_ordinary_machine_has_nothing_in_the_way() {
let (ctx, config, _s) = home("clean");
write(&config.join("settings.json"), json!({"theme": "dark"}));
assert_eq!(overrides(&ctx), Vec::new());
}
#[test]
fn a_settings_file_that_sets_a_key_is_found_where_the_environment_would_not_be() {
let (ctx, config, _s) = home("env-block");
write(
&config.join("settings.json"),
json!({"env": {"ANTHROPIC_API_KEY": "sk-ant-something"}}),
);
let found = overrides(&ctx);
assert_eq!(found.len(), 1, "{found:?}");
assert_eq!(found[0].key, "env.ANTHROPIC_API_KEY");
assert!(found[0].layer.ends_with("settings.json"));
}
#[test]
fn a_helper_that_prints_a_key_is_an_override_whatever_it_prints() {
let (ctx, config, _s) = home("helper");
write(
&config.join("settings.json"),
json!({"apiKeyHelper": "/usr/local/bin/get-key"}),
);
assert_eq!(overrides(&ctx)[0].key, "apiKeyHelper");
}
#[test]
fn a_third_party_provider_is_an_override_too() {
let (ctx, config, _s) = home("bedrock");
write(
&config.join("settings.json"),
json!({"env": {"CLAUDE_CODE_USE_BEDROCK": "1"}}),
);
assert_eq!(overrides(&ctx)[0].key, "env.CLAUDE_CODE_USE_BEDROCK");
}
#[test]
fn a_key_that_is_present_but_empty_sets_nothing() {
let (ctx, config, _s) = home("empty");
write(
&config.join("settings.json"),
json!({
"apiKeyHelper": " ",
"env": {
"ANTHROPIC_API_KEY": "",
"CLAUDE_CODE_USE_BEDROCK": "0",
"CLAUDE_CODE_USE_VERTEX": "false",
},
}),
);
assert_eq!(overrides(&ctx), Vec::new());
}
#[test]
fn a_settings_file_that_is_not_json_is_not_a_reason_to_refuse_anything() {
let (ctx, config, _s) = home("broken");
std::fs::write(config.join("settings.json"), "{ not json").expect("written");
assert_eq!(overrides(&ctx), Vec::new());
}
}
#[cfg(test)]
mod custom_oauth_tests {
use super::tests_support::*;
use super::*;
use serde_json::json;
#[test]
fn a_custom_endpoint_in_a_settings_file_is_found_the_way_the_environment_one_is() {
let (ctx, config, _s) = home("custom-oauth");
assert!(!custom_oauth(&ctx));
write(
&config.join("settings.json"),
json!({"env": {"CLAUDE_CODE_CUSTOM_OAUTH_URL": "https://oauth.example"}}),
);
assert!(custom_oauth(&ctx));
}
}