zeroclaw-labs/zeroclaw · error · anyhow::Error

Unable to allocate non-conflicting key for '{base}'

Error message

Unable to allocate non-conflicting key for '{base}'

What it means

During migration, when an imported key collides with an existing ZeroClaw memory, next_available_key searches for a free name of the form {base}__openclaw_{i}, probing successive i values against the live memory store. If every candidate in the bounded probe range is taken, it bails. Under normal operation this is near-impossible: it requires the entire suffix namespace for a base key to be pre-filled.

Source

Thrown at crates/zeroclaw-runtime/src/migration.rs:349

}

fn normalize_key(key: &str, fallback_idx: usize) -> String {
    let trimmed = key.trim();
    if trimmed.is_empty() {
        return format!("openclaw_{fallback_idx}");
    }
    trimmed.to_string()
}

async fn next_available_key(memory: &dyn Memory, base: &str) -> Result<String> {
    for i in 1..=10_000 {
        let candidate = format!("{base}__openclaw_{i}");
        if memory.get(&candidate).await?.is_none() {
            return Ok(candidate);
        }
    }

    bail!("Unable to allocate non-conflicting key for '{base}'")
}

fn table_columns(conn: &Connection, table: &str) -> Result<Vec<String>> {
    let pragma = format!("PRAGMA table_info({table})");
    let mut stmt = conn.prepare(&pragma)?;
    let rows = stmt.query_map([], |row| row.get::<_, String>(1))?;

    let mut cols = Vec::new();
    for col in rows {
        cols.push(col?.to_ascii_lowercase());
    }

    Ok(cols)
}

fn pick_optional_column_expr(columns: &[String], candidates: &[&str]) -> Option<String> {
    candidates
        .iter()

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. List memories matching the base key and delete or rename the stale __openclaw_ suffixed entries, then re-run
  2. If the target store is disposable, migrate into a fresh workspace instead
  3. If a single normal run triggers this, treat it as a bug — file it with the base key and memory count
Defensive patterns

Strategy: validation

Validate before calling

for i in 0..LIMIT {
    let candidate = format!("{base}__openclaw_{i}");
    if !memory_key_exists(&memory, &candidate).await? {
        return Ok(candidate); // safe to proceed with migration
    }
}
anyhow::bail!("suffix space exhausted for {base}; clean stale __openclaw_ keys");

Try / catch

Err(e) if e.to_string().contains("non-conflicting key") => {
    // list and remove stale {base}__openclaw_* memories from interrupted runs, then re-run once
}

Prevention

When it happens

Trigger: Repeated interrupted migrations that seeded base__openclaw_0..N keys without completing, then re-running migration; a workspace where users (or another tool) deliberately created keys shaped base__openclaw_<i>; adversarial/pathological key sets imported earlier.

Common situations: Migration retried many times after partial failures; scripted loops that rerun migrate on conflict-heavy workspaces; prior imports from another OpenClaw workspace into the same ZeroClaw store.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/b3c93ee746685060. Report an issue: GitHub.