diff --git a/crates/codegraph-cli/tests/batch_m_uninit.rs b/crates/codegraph-cli/tests/batch_m_uninit.rs index a156cb9..fa66c0b 100644 --- a/crates/codegraph-cli/tests/batch_m_uninit.rs +++ b/crates/codegraph-cli/tests/batch_m_uninit.rs @@ -136,7 +136,8 @@ fn run_in_with_config(registry_dir: &Path, args: &[&str], codegraph_dir: Option< .current_dir(registry_dir) .args(args) .env("CODEGRAPH_HTTP_REGISTRY_DIR", registry_dir) - .env("CODEGRAPH_NO_DAEMON", "1"); + .env("CODEGRAPH_NO_DAEMON", "1") + .env("RUST_LOG", "info"); if let Some(configured) = codegraph_dir { command.env("CODEGRAPH_DIR", configured); } else { @@ -790,6 +791,18 @@ fn explicit_init_recovers_an_owner_mismatched_namespace() { "explicit init must recover OwnerMismatch: stdout={}, stderr={}", init.stdout, init.stderr ); + assert!( + !init + .stderr + .contains("replaced stale foreign index state slots") + && !init + .stderr + .contains("replaced stale foreign index database") + && !init.stderr.contains("codegraph_store::rebuild") + && !init.stderr.contains(" INFO "), + "normal init progress must not be split by recovery logger output: stderr={}", + init.stderr + ); assert_eq!(Store::extraction_status(&paths), ExtractionStatus::Current); let store = Store::open_for_read(&paths, deadline(), || false) .expect("recovered OwnerMismatch namespace must be readable"); diff --git a/crates/codegraph-cli/tests/batch_m_v2_namespace.rs b/crates/codegraph-cli/tests/batch_m_v2_namespace.rs index 31f40a1..f0a6a03 100644 --- a/crates/codegraph-cli/tests/batch_m_v2_namespace.rs +++ b/crates/codegraph-cli/tests/batch_m_v2_namespace.rs @@ -84,7 +84,8 @@ fn run_in_env(registry_dir: &Path, args: &[&str], envs: &[(&str, &str)]) -> Run let mut cmd = Command::new(bin()); cmd.args(args) .env("CODEGRAPH_HTTP_REGISTRY_DIR", registry_dir) - .env("CODEGRAPH_NO_DAEMON", "1"); + .env("CODEGRAPH_NO_DAEMON", "1") + .env("RUST_LOG", "info"); for (k, v) in envs { cmd.env(k, v); } @@ -150,8 +151,8 @@ fn init_writes_default_project_local_codegraph_root() { /// A real pre-state-slot SQLite index in the selected root is a rebuildable /// foreign cache, not an unrecoverable contradiction. `init` must acquire its -/// one rebuild lease, replace that database automatically, and explain the -/// one-time replacement at INFO level. +/// one rebuild lease and replace that database automatically without injecting +/// raw logger output into the normal progress UI. #[test] fn init_takes_over_real_sqlite_database_without_state_slots() { let dir = TestDir::new("foreign-db-takeover"); @@ -168,12 +169,10 @@ fn init_takes_over_real_sqlite_database_without_state_slots() { run.stdout, run.stderr ); assert!( - run.stderr.contains("replaced stale foreign index database") - && run - .stderr - .contains(&paths.current_db().display().to_string()) - && run.stderr.contains("missing state slots"), - "INFO log must name the replaced DB and the Missing-state reason: stderr={}", + !run.stderr.contains("replaced stale foreign index database") + && !run.stderr.contains("codegraph_store::rebuild") + && !run.stderr.contains(" INFO "), + "normal init progress must not be split by recovery logger output: stderr={}", run.stderr ); @@ -193,6 +192,36 @@ fn init_takes_over_real_sqlite_database_without_state_slots() { ); } +#[test] +fn foreign_database_replacement_details_require_debug_logging() { + let dir = TestDir::new("foreign-db-debug-log"); + let project = dir.path().join("mini"); + copy_tree(&mini_fixture(), &project); + let paths = IndexPaths::resolve(&project, None).expect("resolve selected index root"); + stage_foreign_database(&paths); + + let run = run_in_env( + dir.path(), + &["init", project.to_str().unwrap()], + &[("RUST_LOG", "codegraph_store::rebuild=debug")], + ); + assert!( + run.ok, + "debug init must still replace a Missing-state SQLite index: stdout={} stderr={}", + run.stdout, run.stderr + ); + assert!( + run.stderr.contains("replaced stale foreign index database") + && run + .stderr + .contains(&paths.current_db().display().to_string()) + && run.stderr.contains("missing state slots") + && run.stderr.contains("DEBUG"), + "explicit debug logging must retain replacement diagnostics: stderr={}", + run.stderr + ); +} + #[test] fn status_json_reports_foreign_database_without_opening_or_mutating_it() { let dir = TestDir::new("foreign-db-status-json"); diff --git a/crates/codegraph-store/src/rebuild.rs b/crates/codegraph-store/src/rebuild.rs index 160dec4..1d8d7a8 100644 --- a/crates/codegraph-store/src/rebuild.rs +++ b/crates/codegraph-store/src/rebuild.rs @@ -336,7 +336,9 @@ fn remove_owner_mismatched_state_slots( .map(|path| path.display().to_string()) .collect::>() .join(", "); - tracing::info!( + // Keep successful cache recovery available for opt-in diagnostics without + // splitting the foreground CLI's progress renderer at the default INFO level. + tracing::debug!( slots = %removed, reason = "project identity mismatch after move or copy", "replaced stale foreign index state slots" @@ -415,7 +417,9 @@ fn begin_from_authorization( remove_database_files(paths, &lease)?; checkpoint(fault, RebuildCheckpoint::DatabaseRemoved)?; if let Some(path) = replaced_artifact { - tracing::info!( + // This is successful internal recovery, not a user-facing warning. + // DEBUG preserves observability without interleaving raw logs with init. + tracing::debug!( path = %path.display(), reason = "missing state slots", "replaced stale foreign index database" diff --git a/skills/codegraph/SKILL.md b/skills/codegraph/SKILL.md index 25a8182..e762a67 100644 --- a/skills/codegraph/SKILL.md +++ b/skills/codegraph/SKILL.md @@ -65,6 +65,12 @@ also escalate through `sync`; do not jump to a forced rebuild merely because the CLI version changed. Follow the exact recovery command printed by `status` or the failed command. +Missing index-state slots and an all-`OwnerMismatch` index after a project move +or copy are supported `init` recovery cases. When `status`, `sync`, or another +failed command explicitly prints `codegraph init ...`, run that exact command to +replace the stale index; do not keep retrying `sync`. Do not generalize this to +mixed or other corruption when the CLI does not prescribe `init`. + ### Start the MCP server ```bash