{"record":{"id":"9dd2bd04da1825d6","repo":"zeroclaw-labs/zeroclaw","slug":"cannot-rename-agent-memory-to-to-an-existing-9dd2bd","errorCode":null,"errorMessage":"cannot rename agent memory to `{to}`: an existing memory store under that alias has {to_rows} row(s); refusing to merge","messagePattern":"cannot rename agent memory to `(.+?)`: an existing memory store under that alias has (.+?) row\\(s\\); refusing to merge","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-memory/src/sqlite.rs","lineNumber":1790,"sourceCode":"            // UUID); only the human `alias` column moves, so this is a single\n            // agents-row update. An unknown `from` matches nothing → Ok(0).\n            //\n            // Collision-safety: `agents.alias` is UNIQUE, and deleting an agent\n            // purges its memories but leaves the `agents` row behind (an orphan\n            // holding the alias). A bare UPDATE onto a previously-used-then-\n            // deleted `to` alias would hit the UNIQUE constraint and fail. We\n            // hold the connection lock across the whole sequence (single writer),\n            // so: refuse if `to` still has memory rows (a genuine conflict we\n            // won't silently merge), otherwise drop the orphan `to` row and\n            // proceed. (`COUNT(*)` over a NULL subselect when no `to` row exists\n            // is 0, so the common no-collision path falls straight through.)\n            let to_rows: i64 = conn.query_row(\n                \"SELECT COUNT(*) FROM memories WHERE agent_id = (SELECT id FROM agents WHERE alias = ?1 LIMIT 1)\",\n                params![to],\n                |row| row.get(0),\n            )?;\n            if to_rows > 0 {\n                anyhow::bail!(\n                    \"cannot rename agent memory to `{to}`: an existing memory store under that alias has {to_rows} row(s); refusing to merge\"\n                );\n            }\n            // Drop any orphan `to` agents row (verified above to own no memories).\n            conn.execute(\"DELETE FROM agents WHERE alias = ?1\", params![to])?;\n            let affected = conn.execute(\n                \"UPDATE agents SET alias = ?2 WHERE alias = ?1\",\n                params![from, to],\n            )?;\n            #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]\n            Ok(affected)\n        })\n        .await?\n    }\n\n    async fn count_agent(&self, agent_alias: &str) -> anyhow::Result<usize> {\n        let conn = self.conn.clone();\n        let agent_alias = agent_alias.to_string();","sourceCodeStart":1772,"sourceCodeEnd":1808,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-memory/src/sqlite.rs#L1772-L1808","documentation":"rename_agent() repoints the agents.alias row, and alias is UNIQUE; the code refuses to silently merge two memory stores. It first counts memories owned by the target alias: more than zero rows raises this error with the count. An orphan target agents row owning no memories is deleted automatically, so only genuine data collisions fail.","triggerScenarios":"Renaming agent A to alias B while B (or a historic B that still owns memory rows) already exists in brain.db.","commonSituations":"Consolidating two agents by renaming one onto the other; re-using a previously active agent's name; scripted renames after re-importing an old workspace.","solutions":["Pick a different, unused alias for the rename","If the target store is disposable: purge it first with purge_agent(target) — its rows are removed, leaving only an orphan agents row that the rename cleans up — then retry","If both stores must survive: export the target's memories (export_agent) before purging, or merge contents under one alias manually","Pre-check occupancy with count_agent(target) before attempting the rename"],"exampleFix":"// before\nmemory.rename_agent(\"old\", \"taken\").await?; // Err: 42 row(s); refusing to merge\n\n// after\nif memory.count_agent(\"taken\").await? > 0 {\n    let archive = memory.export_agent(\"taken\").await?; // keep if needed\n    let _ = archive;\n    memory.purge_agent(\"taken\").await?; // only if disposable\n}\nmemory.rename_agent(\"old\", \"taken\").await?;","handlingStrategy":"validation","validationCode":"// Pre-check target occupancy before rename (count_agent is on the Memory trait)\nif memory.count_agent(to).await? > 0 {\n    anyhow::bail!(\"alias '{to}' already owns memories; choose another alias or purge it first\");\n}","typeGuard":"fn is_rename_merge_refused(e: &anyhow::Error) -> bool {\n    e.to_string().contains(\"refusing to merge\")\n}","tryCatchPattern":"match memory.rename_agent(from, to).await {\n    Err(e) if is_rename_merge_refused(&e) => ask_user_for_disposition(from, to), // purge target, pick new alias, or export+merge\n    other => other,\n}","preventionTips":["Check count_agent(target) before offering a rename in the UI","Never script renames onto unknown aliases without an occupancy check","Export the target's memories (export_agent) before purging in a rename flow","Prefer fresh aliases over recycling recently-used ones"],"tags":["sqlite","agent-rename","unique-constraint","data-safety","rust"],"backgroundTag":"unique-name-conflict","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}