tier: per-path probe diagnostics — surface read errors / parse status in TierInfo.searched

This commit is contained in:
megaproxy 2026-05-09 01:19:52 +01:00
parent c5c38d1ce5
commit 18e55cd139
2 changed files with 80 additions and 38 deletions

View file

@ -5,7 +5,7 @@ use std::path::PathBuf;
use crate::paths::{list_wsl_distros, resolve_roots, ResolvedRoots};
use crate::settings::{save as save_settings, Caps, Settings};
use crate::state::SharedState;
use crate::tier::{detect_in as detect_tier_in, PlanTier};
use crate::tier::{detect_in_with_diagnostics, PlanTier};
use crate::usage::{build_snapshot, UsageSnapshot};
use crate::watch::refresh_and_emit;
@ -111,12 +111,8 @@ pub async fn detect_plan_tier(
candidates.sort();
candidates.dedup();
let tier = detect_tier_in(&candidates);
let (tier, diagnostics) = detect_in_with_diagnostics(&candidates);
let label = tier.label();
let recommended_caps = tier.caps();
Ok(TierInfo { tier, label, recommended_caps, searched: paths_to_strings(&candidates) })
}
fn paths_to_strings(paths: &[PathBuf]) -> Vec<String> {
paths.iter().map(|p| p.display().to_string()).collect()
Ok(TierInfo { tier, label, recommended_caps, searched: diagnostics })
}

View file

@ -79,9 +79,82 @@ fn read_tier_from(path: &std::path::Path) -> Option<PlanTier> {
Some(classify(raw))
}
/// 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()?;
/// Same as [`read_tier_from`] but returns a string describing what happened.
/// Used for the diagnostic `searched` list in TierInfo.
fn probe_tier_file(path: &std::path::Path) -> String {
match std::fs::read(path) {
Err(e) => format!("read_err: {}", e.kind()),
Ok(bytes) => match serde_json::from_slice::<serde_json::Value>(&bytes) {
Err(_) => format!("not_json (size={})", bytes.len()),
Ok(json) => match json.get("organizationRateLimitTier").and_then(|v| v.as_str()) {
None => format!("no_tier_field (size={})", bytes.len()),
Some(raw) => format!("OK: {}", raw),
},
},
}
}
/// 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 {
let (tier, _) = detect_in_with_diagnostics(claude_dirs);
tier
}
/// Like [`detect_in`] but also returns one diagnostic line per probed path
/// describing what happened (for surfacing in the Settings UI).
pub fn detect_in_with_diagnostics(
claude_dirs: &[PathBuf],
) -> (PlanTier, Vec<String>) {
let mut diag: Vec<String> = Vec::new();
let mut found: Option<PlanTier> = None;
for dir in claude_dirs {
let primary = dir.join(".claude.json");
let result = probe_tier_file(&primary);
diag.push(format!("{}{}", primary.display(), result));
if found.is_none() {
if let Some(t) = read_tier_from(&primary) {
found = Some(t);
}
}
let backups = dir.join("backups");
match newest_backup(&backups) {
Err(e) => diag.push(format!("{} → readdir_err: {}", backups.display(), e)),
Ok(None) => diag.push(format!("{} → no_backups", backups.display())),
Ok(Some(p)) => {
let result = probe_tier_file(&p);
diag.push(format!("{}{}", p.display(), result));
if found.is_none() {
if let Some(t) = read_tier_from(&p) {
found = Some(t);
}
}
}
}
if let Some(parent) = dir.parent() {
let alt = parent.join(".claude.json");
let result = probe_tier_file(&alt);
diag.push(format!("{}{}", alt.display(), result));
if found.is_none() {
if let Some(t) = read_tier_from(&alt) {
found = Some(t);
}
}
}
}
(found.unwrap_or(PlanTier::NotFound), diag)
}
fn newest_backup(dir: &std::path::Path) -> std::io::Result<Option<PathBuf>> {
let entries = std::fs::read_dir(dir)?;
let mut latest: Option<(std::time::SystemTime, PathBuf)> = None;
for entry in entries.flatten() {
let path = entry.path();
@ -99,34 +172,7 @@ fn read_tier_from_backups(dir: &std::path::Path) -> Option<PlanTier> {
}
}
}
let (_, path) = latest?;
read_tier_from(&path)
}
/// 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
Ok(latest.map(|(_, p)| p))
}
/// Native-only fallback path. Most callers should prefer `detect_in` with