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
- Reuse the exact candidate_id originally bound to that alias (re-derive it deterministically from content).
- If the existing binding is genuinely wrong, run a data migration to delete/rewrite the harness_candidate_aliases row, then re-register.
- 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
- Derive candidate_id deterministically from content so re-registration under the same alias always targets the same value.
- Treat aliases as write-once in your data model and never attempt to repoint them in normal flows.
- If a binding must change, do it via an explicit, logged migration that deletes the old row first.
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
- candidate alias collision with physical candidate id
- legacy candidate id collision with different immutable conte
- candidate id collision with different immutable content
- an active harness configuration already exists
- baseline is not the active harness configuration
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/bc203556f0449423.
Report an issue: GitHub.