affaan-m/ECC · error · anyhow::Error

candidate alias collision with different immutable target

Error message

candidate alias collision with different immutable target

What it means

Thrown by register_harness_alias when alias_id already exists in harness_candidate_aliases but points to a different candidate_id than the one requested. Aliases are immutable once bound: the same alias cannot be repointed to a new canonical target.

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 01e15490f0)

Solutions

  1. Reuse the exact candidate_id originally bound to that alias (re-derive it deterministically from content).
  2. If the existing binding is genuinely wrong, run a data migration to delete/rewrite the harness_candidate_aliases row, then re-register.
  3. Make alias assignment a pure function of content so retries are idempotent and never produce a conflicting target.
Defensive patterns

Strategy: validation

Validate before calling

fn existing_alias_target(tx: &rusqlite::Transaction, alias_id: &str) -> Result<Option<String>> {
    Ok(tx.query_row(
        "SELECT candidate_id FROM harness_candidate_aliases WHERE alias_id = ?1",
        [alias_id], |row| row.get::<_, String>(0)).optional()?)
}

// before registering:
if let Some(prior) = existing_alias_target(&tx, alias_id)? {
    if prior != candidate_id {
        return Err(anyhow::anyhow!("alias {alias_id} already bound to {prior}; refusing to repoint"));
    }
}

Type guard

fn alias_is_compatible(tx: &rusqlite::Transaction, alias_id: &str, candidate_id: &str) -> bool {
    match existing_alias_target(tx, alias_id) {
        Ok(Some(prior)) => prior == candidate_id,
        Ok(None) => true,
        Err(_) => false,
    }
}

Try / catch

match store.register_harness_alias(&tx, alias_id, candidate_id) {
    Ok(()) => { /* ok */ }
    Err(e) if e.to_string().contains("different immutable target") => {
        // the alias is permanently bound elsewhere; do not retry with a new target,
        // either accept the prior target or run a data migration.
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling register_harness_alias with an alias_id whose stored candidate_id differs from the passed candidate_id. Idempotent re-registration with the same target returns Ok(()); a different target bails.

Common situations: Re-running a migration/registration with content that changed under the same legacy id; two different canonical configs claiming the same legacy id; concurrent registrations racing on the same alias.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/bc203556f0449423. Report an issue: GitHub.