fix(analyze): gate up-to-date self-heal on --allow-duplicate-name (#2264)

The prior commit healed every up-to-date-but-unregistered repo by falling through
to register it — which broke the #1169 guard: a plain `analyze` of an up-to-date
repo whose registry entry is missing MUST fail loudly ("Analysis did not finalize")
rather than silently register a possibly half-finalized index.

Distinguish the two causes of "unregistered":
- collision-rejected + user re-runs with --allow-duplicate-name → explicit intent
  to register, so fall through to the pipeline and register it (#829).
- plain analyze, registry missing/wiped → keep the #1169 fail-loud behavior.

So self-heal is gated on options.allowDuplicateName; isRepoRegistered is only read
on that opt-in branch, so the common fast path keeps its single-stat cost. Both
cli-e2e guards (#1169 fail-loud, #829 heal) now pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JBJomjoTdBV2eveDVq4JMm
This commit is contained in:
Gergo Magyar 2026-06-21 10:32:53 +00:00
parent 7906464ae5
commit 6514c4ab0e

View file

@ -778,15 +778,21 @@ export async function runFullAnalysis(
return true; // conservative on git failure
}
})();
// Only short-circuit when this repo is actually REGISTERED. A prior run
// can write meta.json and then fail before registerRepo (e.g. a rejected
// --name collision), leaving the index up-to-date but UNREGISTERED. Taking
// the fast path there returns an unregistered repo that the CLI's
// assertAnalysisFinalized rejects — and `--allow-duplicate-name` could
// never heal it (it would keep hitting this early-return). Fall through to
// the pipeline so it gets registered (honoring allowDuplicateName); already
// registered repos keep the fast path unchanged (#2264).
if (!dirty && (await isRepoRegistered(repoPath))) {
// Registration wrinkle around the fast path (#2264). A prior
// `analyze --name X` that hit a name collision writes meta.json (meta-save
// runs before registerRepo) then fails before registering, leaving the
// index up-to-date but UNREGISTERED. When the user re-runs with
// --allow-duplicate-name they explicitly want it registered, so fall
// through to the pipeline (which registers it, honoring the flag) instead
// of early-returning an unregistered repo the flag could never heal.
// For a PLAIN analyze we deliberately do NOT self-heal: an up-to-date but
// unregistered repo early-returns here and the CLI's assertAnalysisFinalized
// surfaces it as a hard failure (#1169) rather than silently registering a
// possibly half-finalized index. `isRepoRegistered` is only read on the
// opt-in branch so the common fast path keeps its single-stat cost.
const healUnregistered =
options.allowDuplicateName === true && !(await isRepoRegistered(repoPath));
if (!dirty && !healUnregistered) {
await ensureGitNexusIgnored(repoPath);
return {
// `resolveRepoIdentityRoot` collapses worktree roots to the