diff --git a/CHANGELOG.md b/CHANGELOG.md index 756caa3..9a6c5f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 +## [1.0.153] - 2026-06-02 + +### Added + +- **Auto-prune stale repos during Phase 1 warmup** — when a repo fails warmup + because its path or database no longer exists, `codesearch serve` now + automatically removes it from `repos.json` and logs a warning, instead of + silently retrying on every restart. Works in concert with the relocation pass + (reconcile_all_paths): relocatable repos are rewritten first, truly missing + ones are pruned. + +### Fixed + +- **Missing `YELLOW` color variable in `scripts/qc.sh`** — the variable was + referenced but never declared, causing a visual glitch in QC output. + ## [1.0.152] - 2026-06-02 ### Added diff --git a/Cargo.lock b/Cargo.lock index 5d32baf..386b4ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -628,7 +628,7 @@ dependencies = [ [[package]] name = "codesearch" -version = "1.0.152" +version = "1.0.153" dependencies = [ "anyhow", "arroy", diff --git a/Cargo.toml b/Cargo.toml index 33d2449..a59dd0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "codesearch" -version = "1.0.152" +version = "1.0.153" edition = "2021" authors = ["codesearch contributors"] license = "Apache-2.0" diff --git a/scripts/qc.sh b/scripts/qc.sh index 8892780..d8ea313 100644 --- a/scripts/qc.sh +++ b/scripts/qc.sh @@ -26,6 +26,7 @@ GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' GRAY='\033[0;90m' +YELLOW='\033[1;33m' NC='\033[0m' FAILED=() diff --git a/src/serve/mod.rs b/src/serve/mod.rs index eb0fac8..8e31d0d 100644 --- a/src/serve/mod.rs +++ b/src/serve/mod.rs @@ -610,20 +610,74 @@ impl ServeState { } /// Phase 1: warm all repos sequentially, awaiting incremental refresh per repo. + /// Stale entries (database missing or repo path gone) are auto-pruned from repos.json. pub(crate) async fn run_phase_1_warmup_all(self: &Arc) { let aliases = self.aliases(); if aliases.is_empty() { return; } info!("🔥 Phase 1 warmup: {} repos (no FSW)", aliases.len()); + + let mut pruned: Vec = Vec::new(); + for alias in &aliases { match self.warmup_repo(alias).await { Ok(()) => info!("phase-1: warmed '{}'", alias), - Err(e) => warn!("phase-1: warmup '{}' failed: {}", alias, e), + Err(e) => { + // Auto-prune stale entries where the database or repo path no longer exists. + let should_prune = { + let config = self.config.read().ok(); + let path = config.as_ref().and_then(|c| c.resolve(alias)); + match path { + Some(p) => { + let db_missing = !p.join(DB_DIR_NAME).exists(); + let path_gone = !p.exists(); + db_missing || path_gone + } + None => { + // Alias resolves to nothing — definitely stale + true + } + } + }; + + if should_prune { + warn!("phase-1: pruning stale alias '{}' — {}", alias, e); + // Clean up any residual in-memory state + let _ = self.stop_fsw(alias); + self.repos.remove(alias); + self.last_access.remove(alias); + + // Unregister from repos.json + if let Ok(mut config) = self.config.write() { + if config.unregister_alias(alias) { + if let Err(save_err) = config.save() { + warn!( + "phase-1: failed to save repos.json after pruning '{}': {}", + alias, save_err + ); + } else { + pruned.push(alias.clone()); + } + } + } + } else { + warn!("phase-1: warmup '{}' failed: {}", alias, e); + } + } } tokio::time::sleep(Duration::from_millis(200)).await; } - info!("🔥 Phase 1 warmup complete"); + + if !pruned.is_empty() { + info!( + "🔥 Phase 1 warmup complete (pruned {} stale: {})", + pruned.len(), + pruned.join(", ") + ); + } else { + info!("🔥 Phase 1 warmup complete"); + } } /// Phase 2: semaphore-bounded concurrent C# SCIP rebuilds, sorted by recency.