zeroclaw-labs/zeroclaw · error

cannot rename agent memory to `{to}`: an existing memory sto

Error message

cannot rename agent memory to `{to}`: an existing memory store under that alias has {to_rows} row(s); refusing to merge

What it means

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.

Source

Thrown at crates/zeroclaw-memory/src/sqlite.rs:1790

            // UUID); only the human `alias` column moves, so this is a single
            // agents-row update. An unknown `from` matches nothing → Ok(0).
            //
            // Collision-safety: `agents.alias` is UNIQUE, and deleting an agent
            // purges its memories but leaves the `agents` row behind (an orphan
            // holding the alias). A bare UPDATE onto a previously-used-then-
            // deleted `to` alias would hit the UNIQUE constraint and fail. We
            // hold the connection lock across the whole sequence (single writer),
            // so: refuse if `to` still has memory rows (a genuine conflict we
            // won't silently merge), otherwise drop the orphan `to` row and
            // proceed. (`COUNT(*)` over a NULL subselect when no `to` row exists
            // is 0, so the common no-collision path falls straight through.)
            let to_rows: i64 = conn.query_row(
                "SELECT COUNT(*) FROM memories WHERE agent_id = (SELECT id FROM agents WHERE alias = ?1 LIMIT 1)",
                params![to],
                |row| row.get(0),
            )?;
            if to_rows > 0 {
                anyhow::bail!(
                    "cannot rename agent memory to `{to}`: an existing memory store under that alias has {to_rows} row(s); refusing to merge"
                );
            }
            // Drop any orphan `to` agents row (verified above to own no memories).
            conn.execute("DELETE FROM agents WHERE alias = ?1", params![to])?;
            let affected = conn.execute(
                "UPDATE agents SET alias = ?2 WHERE alias = ?1",
                params![from, to],
            )?;
            #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
            Ok(affected)
        })
        .await?
    }

    async fn count_agent(&self, agent_alias: &str) -> anyhow::Result<usize> {
        let conn = self.conn.clone();
        let agent_alias = agent_alias.to_string();

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Pick a different, unused alias for the rename
  2. 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
  3. If both stores must survive: export the target's memories (export_agent) before purging, or merge contents under one alias manually
  4. Pre-check occupancy with count_agent(target) before attempting the rename

Example fix

// before
memory.rename_agent("old", "taken").await?; // Err: 42 row(s); refusing to merge

// after
if memory.count_agent("taken").await? > 0 {
    let archive = memory.export_agent("taken").await?; // keep if needed
    let _ = archive;
    memory.purge_agent("taken").await?; // only if disposable
}
memory.rename_agent("old", "taken").await?;
Defensive patterns

Strategy: validation

Validate before calling

// Pre-check target occupancy before rename (count_agent is on the Memory trait)
if memory.count_agent(to).await? > 0 {
    anyhow::bail!("alias '{to}' already owns memories; choose another alias or purge it first");
}

Type guard

fn is_rename_merge_refused(e: &anyhow::Error) -> bool {
    e.to_string().contains("refusing to merge")
}

Try / catch

match memory.rename_agent(from, to).await {
    Err(e) if is_rename_merge_refused(&e) => ask_user_for_disposition(from, to), // purge target, pick new alias, or export+merge
    other => other,
}

Prevention

When it happens

Trigger: Renaming agent A to alias B while B (or a historic B that still owns memory rows) already exists in brain.db.

Common situations: 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.

Related errors


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