{"record":{"id":"031d58a59dd3498b","repo":"zeroclaw-labs/zeroclaw","slug":"qdrant-delete-failed-status-text","errorCode":null,"errorMessage":"Qdrant delete failed ({status}): {text}","messagePattern":"Qdrant delete failed \\((.+?)\\): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-memory/src/qdrant.rs","lineNumber":491,"sourceCode":"    async fn delete_points_matching(&self, fields: &[(&str, &str)]) -> Result<bool> {\n        self.ensure_initialized().await?;\n\n        let delete_body = serde_json::json!({\"filter\": Self::must_filter(fields)});\n        let resp = self\n            .request(\n                reqwest::Method::POST,\n                &format!(\"/collections/{}/points/delete\", self.collection),\n            )\n            .query(&[(\"wait\", \"true\")])\n            .json(&delete_body)\n            .send()\n            .await\n            .context(\"failed to delete from Qdrant\")?;\n\n        if !resp.status().is_success() {\n            let status = resp.status();\n            let text = resp.text().await.unwrap_or_default();\n            anyhow::bail!(\"Qdrant delete failed ({status}): {text}\");\n        }\n\n        Ok(true)\n    }\n}\n\n/// Qdrant point payload structure\n#[derive(Debug, Clone, Serialize, Deserialize)]\nstruct MemoryPayload {\n    key: String,\n    content: String,\n    category: String,\n    timestamp: String,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    session_id: Option<String>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    agent_id: Option<String>,\n}","sourceCodeStart":473,"sourceCodeEnd":509,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-memory/src/qdrant.rs#L473-L509","documentation":"delete_points_matching POSTs /collections/{c}/points/delete with a filter; it implements forget(), forget_for_agent(), purge_session_for_agent(), purge_agent(), and the supersede step of store_with_agent(). This error means Qdrant refused the delete with a non-2xx status.","triggerScenarios":"404 collection missing (dropped after init); 400 malformed filter; 409/write-lock timeout under heavy upsert load; 5xx during Qdrant maintenance or optimization.","commonSituations":"Forgetting memories while the collection was rebuilt; deleting during heavy write traffic; Qdrant under memory pressure returning 503.","solutions":["Check the status in the message; 404 means recreate the collection and restart the process so init re-runs","5xx/503: retry the forget after load drops or Qdrant reports ready — deletes are idempotent","400: inspect key/session_id values for characters that break the filter JSON","Repeated lock timeouts: review Qdrant optimizer and memory settings"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":"fn is_qdrant_delete_error(e: &anyhow::Error) -> bool {\n    e.to_string().starts_with(\"Qdrant delete failed\")\n}","tryCatchPattern":"for attempt in 0..3 {\n    match memory.forget(key).await {\n        Ok(n) => break,\n        Err(e) if is_qdrant_delete_error(&e) && attempt < 2 => { tokio::time::sleep(backoff(attempt)).await; }\n        Err(e) => return Err(e),\n    }\n} // deletes are idempotent; a retry cannot double-delete","preventionTips":["Retry deletes with backoff on 5xx — they are idempotent","Avoid rebuilding collections while purges run","Alert on repeated 404s (collection dropped after init)"],"tags":["qdrant","delete","http-status","rust"],"backgroundTag":"qdrant-api-error","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}