{"record":{"id":"b3c93ee746685060","repo":"zeroclaw-labs/zeroclaw","slug":"unable-to-allocate-non-conflicting-key-for-base","errorCode":null,"errorMessage":"Unable to allocate non-conflicting key for '{base}'","messagePattern":"Unable to allocate non-conflicting key for '(.+?)'","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-runtime/src/migration.rs","lineNumber":349,"sourceCode":"}\n\nfn normalize_key(key: &str, fallback_idx: usize) -> String {\n    let trimmed = key.trim();\n    if trimmed.is_empty() {\n        return format!(\"openclaw_{fallback_idx}\");\n    }\n    trimmed.to_string()\n}\n\nasync fn next_available_key(memory: &dyn Memory, base: &str) -> Result<String> {\n    for i in 1..=10_000 {\n        let candidate = format!(\"{base}__openclaw_{i}\");\n        if memory.get(&candidate).await?.is_none() {\n            return Ok(candidate);\n        }\n    }\n\n    bail!(\"Unable to allocate non-conflicting key for '{base}'\")\n}\n\nfn table_columns(conn: &Connection, table: &str) -> Result<Vec<String>> {\n    let pragma = format!(\"PRAGMA table_info({table})\");\n    let mut stmt = conn.prepare(&pragma)?;\n    let rows = stmt.query_map([], |row| row.get::<_, String>(1))?;\n\n    let mut cols = Vec::new();\n    for col in rows {\n        cols.push(col?.to_ascii_lowercase());\n    }\n\n    Ok(cols)\n}\n\nfn pick_optional_column_expr(columns: &[String], candidates: &[&str]) -> Option<String> {\n    candidates\n        .iter()","sourceCodeStart":331,"sourceCodeEnd":367,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-runtime/src/migration.rs#L331-L367","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["List memories matching the base key and delete or rename the stale __openclaw_ suffixed entries, then re-run","If the target store is disposable, migrate into a fresh workspace instead","If a single normal run triggers this, treat it as a bug — file it with the base key and memory count"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"for i in 0..LIMIT {\n    let candidate = format!(\"{base}__openclaw_{i}\");\n    if !memory_key_exists(&memory, &candidate).await? {\n        return Ok(candidate); // safe to proceed with migration\n    }\n}\nanyhow::bail!(\"suffix space exhausted for {base}; clean stale __openclaw_ keys\");","typeGuard":null,"tryCatchPattern":"Err(e) if e.to_string().contains(\"non-conflicting key\") => {\n    // list and remove stale {base}__openclaw_* memories from interrupted runs, then re-run once\n}","preventionTips":["Make migrations idempotent: record imported keys or clean __openclaw_ suffixed entries on restart","Alert when a workspace accumulates many __openclaw_ suffixed keys — sign of repeated partial runs","Prefer migrating into a fresh target workspace when retrying after failures"],"tags":["rust","zeroclaw","migration","memory","key-conflict"],"backgroundTag":"name-collision","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}