Detect tier across WSL roots, not just native home (\\wsl$\<distro>\... probe)

This commit is contained in:
megaproxy 2026-05-09 01:00:50 +01:00
parent ef84257ddd
commit f33bb5481b
3 changed files with 86 additions and 34 deletions

View file

@ -70,23 +70,18 @@ fn classify(raw: &str) -> PlanTier {
}
}
/// Find the most recent on-disk source of `organizationRateLimitTier`.
///
/// Search order:
/// 1. `~/.claude/.claude.json` if it exists (Linux / WSL)
/// 2. `%USERPROFILE%\.claude\.claude.json` (Windows native — same logic via dirs::home_dir)
/// 3. The newest `~/.claude/backups/.claude.json.backup.*` (this is what
/// we observed populating in practice — `.claude.json` itself isn't
/// always present, but the backup directory tracks every config change)
fn locate_claude_json() -> Option<PathBuf> {
let home = dirs::home_dir()?;
let primary = home.join(".claude").join(".claude.json");
if primary.is_file() {
return Some(primary);
}
/// Read the tier from a single `.claude.json`-shaped file. None if the file
/// doesn't exist, isn't valid JSON, or is missing the field.
fn read_tier_from(path: &std::path::Path) -> Option<PlanTier> {
let bytes = std::fs::read(path).ok()?;
let json: serde_json::Value = serde_json::from_slice(&bytes).ok()?;
let raw = json.get("organizationRateLimitTier").and_then(|v| v.as_str())?;
Some(classify(raw))
}
let backups_dir = home.join(".claude").join("backups");
let entries = std::fs::read_dir(&backups_dir).ok()?;
/// Find the newest `.claude.json.backup.*` in `dir` and read its tier.
fn read_tier_from_backups(dir: &std::path::Path) -> Option<PlanTier> {
let entries = std::fs::read_dir(dir).ok()?;
let mut latest: Option<(std::time::SystemTime, PathBuf)> = None;
for entry in entries.flatten() {
let path = entry.path();
@ -104,23 +99,43 @@ fn locate_claude_json() -> Option<PathBuf> {
}
}
}
latest.map(|(_, p)| p)
let (_, path) = latest?;
read_tier_from(&path)
}
pub fn detect() -> PlanTier {
let Some(path) = locate_claude_json() else {
return PlanTier::NotFound;
};
let Ok(bytes) = std::fs::read(&path) else {
return PlanTier::NotFound;
};
let Ok(json) = serde_json::from_slice::<serde_json::Value>(&bytes) else {
return PlanTier::NotFound;
};
match json.get("organizationRateLimitTier").and_then(|v| v.as_str()) {
Some(raw) => classify(raw),
None => PlanTier::NotFound,
/// Try each `.claude/` candidate directory in order. The first hit wins.
///
/// For each candidate `<dir>` we look at:
/// 1. `<dir>/.claude.json` (e.g. ~/.claude/.claude.json)
/// 2. `<dir>/backups/.claude.json.backup.*` (newest)
/// 3. `<parent of dir>/.claude.json` (some setups put it at ~/.claude.json)
pub fn detect_in(claude_dirs: &[PathBuf]) -> PlanTier {
for dir in claude_dirs {
let primary = dir.join(".claude.json");
if let Some(t) = read_tier_from(&primary) {
return t;
}
let backups = dir.join("backups");
if let Some(t) = read_tier_from_backups(&backups) {
return t;
}
if let Some(parent) = dir.parent() {
let alt = parent.join(".claude.json");
if let Some(t) = read_tier_from(&alt) {
return t;
}
}
}
PlanTier::NotFound
}
/// Native-only fallback path. Most callers should prefer `detect_in` with
/// the resolved-roots' parents so the WSL case works.
pub fn detect() -> PlanTier {
let Some(home) = dirs::home_dir() else {
return PlanTier::NotFound;
};
detect_in(&[home.join(".claude")])
}
#[cfg(test)]