{"record":{"id":"6f2ad3250448e5db","repo":"zeroclaw-labs/zeroclaw","slug":"purge-agent-not-supported-by-this-memory-backend","errorCode":null,"errorMessage":"purge_agent not supported by this memory backend","messagePattern":"purge_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":352,"sourceCode":"    /// override this; agent-scoped wrappers use it instead of composing a\n    /// session list with key-only deletes.\n    async fn purge_session_for_agent(\n        &self,\n        _session_id: &str,\n        _agent_id: &str,\n    ) -> anyhow::Result<usize> {\n        anyhow::bail!(\"purge_session_for_agent not supported by this memory backend\")\n    }\n\n    /// Remove every memory row attributed to the given agent alias.\n    /// Returns the number of deleted entries. Called when an agent alias is\n    /// removed from `[agents.<alias>]` so the database doesn't accumulate\n    /// rows for retired aliases.\n    /// Default: returns unsupported error. Backends with per-agent storage\n    /// (sqlite, postgres) override this; backends without (markdown, none)\n    /// keep the default and the caller logs a warning.\n    async fn purge_agent(&self, _agent_alias: &str) -> anyhow::Result<usize> {\n        anyhow::bail!(\"purge_agent not supported by this memory backend\")\n    }\n\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.","sourceCodeStart":334,"sourceCodeEnd":370,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-api/src/memory_traits.rs#L334-L370","documentation":"DeviceRegistry::new warms an in-memory cache by preparing a SELECT over every devices column (token_hash, id, name, device_type, paired_at, last_seen, ip_address, capabilities). prepare fails when the SQL cannot compile against the existing schema — classically when devices.db predates a column (only capabilities has an additive ALTER TABLE migration in this constructor), or the file is not a valid SQLite database at all. The expect panics the constructor at startup.","triggerScenarios":"An older devices.db from a previous build whose devices table lacks a column referenced by the SELECT; a truncated or corrupt devices.db; a non-SQLite file left at the path.","commonSituations":"Upgrading the gateway across versions on a long-lived workspace where the pairing schema evolved; restoring devices.db from a partial backup; the additive-migration pattern in this file only covers the capabilities column, so any other drift resurfaces here.","solutions":["Rename or delete devices.db and restart — the registry rebuilds empty and devices re-pair (pairing data is a cache, not authoritative identity).","If preserving pairings matters, inspect the table (`sqlite3 devices.db '.schema devices'`) and add missing columns manually, mirroring the additive ALTER pattern used for capabilities.","Verify file integrity with `sqlite3 devices.db 'PRAGMA integrity_check;'`.","As a maintainer, add an additive migration per new column and return Result with schema details on failure."],"exampleFix":"// before\nlet mut stmt = conn\n    .prepare(\"SELECT token_hash, id, name, device_type, paired_at, last_seen, ip_address, capabilities FROM devices\")\n    .expect(\"Failed to prepare device select\");\n\n// after (maintainer fix: additive migration + Result)\nlet _ = conn.execute(\"ALTER TABLE devices ADD COLUMN ip_address TEXT\", []);\nlet mut stmt = conn\n    .prepare(\"SELECT ... FROM devices\")\n    .with_context(|| \"device registry schema drifted; rename devices.db to re-pair\")?;","handlingStrategy":"validation","validationCode":"// Fail with an actionable message instead of the prepare panic:\nfn devices_schema_ok(conn: &rusqlite::Connection) -> bool {\n    conn.prepare(\n        \"SELECT token_hash, id, name, device_type, paired_at, last_seen, ip_address, capabilities FROM devices\",\n    )\n    .is_ok()\n}\n// Run against the existing devices.db before handing it to DeviceRegistry::new.","typeGuard":null,"tryCatchPattern":"let reg = std::panic::catch_unwind(|| DeviceRegistry::new(&workspace_dir));\nif reg.is_err() {\n    // likely schema drift: back up devices.db, delete it, restart, re-pair devices\n}","preventionTips":["Treat devices.db as a disposable cache: back it up, but be ready to delete and re-pair after upgrades","Check `.schema devices` with the sqlite3 CLI after version upgrades","Maintainers: add an additive ALTER per new column so upgrades never hit the prepare panic","Keep workspace state versioned so schema drift is detectable before boot"],"tags":["rust","sqlite","rusqlite","schema-mismatch","migration","panic"],"backgroundTag":"sqlite-schema-mismatch","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}