{"record":{"id":"935dadd8250e3407","repo":"zeroclaw-labs/zeroclaw","slug":"rename-agent-not-supported-by-this-memory-backend","errorCode":null,"errorMessage":"rename_agent not supported by this memory backend","messagePattern":"rename_agent not supported by this memory backend","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"critical","filePath":"crates/zeroclaw-api/src/memory_traits.rs","lineNumber":372,"sourceCode":"\n    /// Export every memory row attributed to `agent_alias`, for the agent-\n    /// deletion archive (export-then-delete,). Pairs with\n    /// [`Self::purge_agent`]: the surface exports these rows to the archive,\n    /// then purges. Default: empty (backends without per-agent export).\n    async fn export_agent(&self, _agent_alias: &str) -> anyhow::Result<Vec<MemoryEntry>> {\n        Ok(Vec::new())\n    }\n\n    /// Re-point every memory row from the `from` alias to the `to` alias,\n    /// returning the number of rows re-pointed. Called when an alias is renamed.\n    /// For the SQL backends (sqlite/postgres) memory rows ride the\n    /// agent's UUID, so this is a single `UPDATE agents SET alias` and the count\n    /// is the agents-row count (0 or 1); payload-keyed backends (qdrant) rewrite\n    /// the alias on every matching memory point and return that count.\n    /// Default: unsupported error; backends with per-agent storage override.\n    /// Markdown/none keep the default and the caller logs a warning.\n    async fn rename_agent(&self, _from: &str, _to: &str) -> anyhow::Result<usize> {\n        anyhow::bail!(\"rename_agent not supported by this memory backend\")\n    }\n\n    /// Read-only residue probe for the agent-rename cascade: the count\n    /// of state [`Self::rename_agent`] WOULD re-point for `agent_alias`, without\n    /// mutating anything. Used by the gateway to tell a genuine post-persist\n    /// partial failure (state still lagging at the old alias) apart from an\n    /// unrelated request, so a resume only fires on real residue.\n    ///\n    /// MUST mirror exactly what `rename_agent` moves: for the SQL backends that\n    /// is the `agents` row (alias presence), NOT the memory-row count - an agent\n    /// with an `agents` row but zero memory rows still gets re-pointed, so a\n    /// memory-row probe would be a false negative. Default 0 (markdown/none have\n    /// no DB rows and their `rename_agent` is a no-op).\n    async fn count_agent(&self, _agent_alias: &str) -> anyhow::Result<usize> {\n        Ok(0)\n    }\n\n    /// Count total memories","sourceCodeStart":354,"sourceCodeEnd":390,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-api/src/memory_traits.rs#L354-L390","documentation":"Final step of the DeviceRegistry::new warm-up: query_map executes the SELECT and builds the cache rows. The expect fires on runtime errors while running the query — most commonly SQLITE_BUSY ('database is locked') when another process or connection holds devices.db, and otherwise I/O errors such as disk full or a truncated file. Like the other constructor expects, it panics registry construction, taking pairing and startup with it.","triggerScenarios":"A second gateway instance (or an external sqlite3 session) holds devices.db while this process warms its cache; the filesystem runs out of space mid-query; the file is truncated after a hard crash.","commonSituations":"Two gateway processes pointed at the same workspace during a migration or restart overlap; an operator inspecting devices.db with the sqlite3 CLI while the gateway starts; disk-full events on the workspace volume.","solutions":["Ensure exactly one gateway instance runs per workspace; stop overlapping processes or supervisors mid-restart.","Close external sqlite3 sessions on devices.db before starting the gateway.","Check free space on the workspace volume and integrity (`PRAGMA integrity_check;`).","Retry startup once the competing holder exits — WAL permits concurrent readers, so a persistent failure means a writer is holding the database exclusively.","As a maintainer, return Result and retry briefly on SQLITE_BUSY during warm-up."],"exampleFix":"// before\nlet rows = stmt\n    .query_map([], |row| { ... })\n    .expect(\"Failed to query devices\");\n\n// after (maintainer fix)\nlet rows = stmt\n    .query_map([], |row| { ... })\n    .context(\"failed to warm device registry cache (devices.db locked or I/O error)\")?;","handlingStrategy":"validation","validationCode":"// Advisory lock so two gateways never share devices.db:\nfn workspace_exclusive(dir: &std::path::Path) -> bool {\n    use std::os::unix::io::AsRawFd;\n    let f = std::fs::OpenOptions::new()\n        .write(true)\n        .create(true)\n        .open(dir.join(\".gateway.lock\"))\n        .ok();\n    match f {\n        Some(f) => unsafe { libc::flock(f.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) == 0 },\n        None => false,\n    }\n}","typeGuard":null,"tryCatchPattern":"let reg = std::panic::catch_unwind(|| DeviceRegistry::new(&workspace_dir));\nif reg.is_err() {\n    // check for a second gateway or sqlite3 session holding devices.db, then retry once cleared\n}","preventionTips":["Enforce single-instance-per-workspace with a lock file or process supervisor","Close external sqlite3 sessions before starting the gateway","Restart sequentially during migrations so old and new processes never overlap","Monitor disk space; SQLITE_FULL and SQLITE_IO errors surface in this same warm-up query"],"tags":["rust","sqlite","rusqlite","database-locked","concurrency","panic"],"backgroundTag":"database-locked","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}