affaan-m/ECC · error

candidate alias collision with different immutable target

Error message

candidate alias collision with different immutable target

What it means

Candidate aliases are immutable: once an alias_id maps to a candidate_id it can never be re-pointed. Registration loads any existing mapping for the alias; if the existing target differs from the requested candidate_id it bails 'candidate alias collision with different immutable target'. If it matches, registration is idempotent and returns Ok.

Solutions

  1. Look up the existing mapping first and only register when the target matches (the API already short-circuits idempotently).
  2. Derive new alias strings when the target candidate changes (version-suffixed aliases) instead of reusing the old one.
  3. Centralize alias creation behind one service/lock to avoid concurrent conflicting claims.
  4. If you truly need to move an alias, add an explicit re-point API with deprecation of the old target — do not overwrite.

Example fix

// before
// blindly re-registering after re-keying the candidate
store.register_candidate_alias(&new_candidate_id, "stable-alias")?; // bails

// after
match store.lookup_candidate_by_alias("stable-alias")? {
    Some(id) if id != new_candidate_id => {
        let alias = format!("stable-alias@{}", new_candidate_id);
        store.register_candidate_alias(&new_candidate_id, &alias)?;
    }
    _ => store.register_candidate_alias(&new_candidate_id, "stable-alias")?,
}
Defensive patterns

Strategy: validation

Validate before calling

if let Some(existing) = store.lookup_candidate_by_alias(alias_id)? {
    ensure!(existing == candidate_id, "alias {alias_id} is immutable and points to {existing}");
} // else safe to register (API is idempotent for matching targets)

Try / catch

match store.register_candidate_alias(&cid, alias) {
    Err(e) if e.to_string().contains("different immutable target") => {
        mint_new_versioned_alias(alias) // never re-point
    }
    other => other,
}

Prevention

When it happens

Trigger: registering the same alias_id for two different candidate_ids — e.g. reusing a well-known alias string for a new candidate, retrying registration after re-keying a candidate, or concurrent writers racing with different targets.

Common situations: Re-deploying configs where an alias was reassigned to a renamed candidate; build/tooling systems reusing stable short names across versions; two services independently claiming the same alias for different candidates.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/bc203556f0449423. Report an issue: GitHub.

Appendix: source

Thrown at ecc2/src/session/store.rs:5318

                "SELECT 1 FROM harness_candidates WHERE id = ?1",
                [alias_id],
                |_| Ok(()),
            )
            .optional()?
            .is_some()
        {
            anyhow::bail!("candidate alias collision with physical candidate id");
        }
        let existing = tx
            .query_row(
                "SELECT candidate_id FROM harness_candidate_aliases WHERE alias_id = ?1",
                [alias_id],
                |row| row.get::<_, String>(0),
            )
            .optional()?;
        if let Some(existing) = existing {
            if existing != candidate_id {
                anyhow::bail!("candidate alias collision with different immutable target");
            }
            return Ok(());
        }
        tx.execute(
            "INSERT INTO harness_candidate_aliases (alias_id, candidate_id, id_version, created_at) VALUES (?1, ?2, 2, ?3)",
            rusqlite::params![alias_id, candidate_id, chrono::Utc::now().to_rfc3339()],
        )?;
        Ok(())
    }

    fn resolve_harness_candidate_id(connection: &Connection, candidate_id: &str) -> Result<String> {
        if let Some(target) = connection
            .query_row(
                "SELECT candidate_id FROM harness_candidate_aliases WHERE alias_id = ?1",
                [candidate_id],
                |row| row.get::<_, String>(0),
            )
            .optional()?

View on GitHub (pinned to 8321021c54)