zeroclaw-labs/zeroclaw · error

Qdrant upsert failed ({status}): {text}

Error message

Qdrant upsert failed ({status}): {text}

What it means

store_with_agent() upserts a freshly generated UUID point (vector + payload) via PUT /collections/{c}/points?wait=true. A non-2xx on that upsert raises this error. Because the point id is generated per call, blindly retrying store() creates a duplicate entry — fix the root cause or dedupe instead.

Source

Thrown at crates/zeroclaw-memory/src/qdrant.rs:977

                "payload": payload
            }]
        });

        let resp = self
            .request(
                reqwest::Method::PUT,
                &format!("/collections/{}/points", self.collection),
            )
            .query(&[("wait", "true")])
            .json(&upsert_body)
            .send()
            .await
            .context("failed to upsert point to Qdrant")?;

        if !resp.status().is_success() {
            let status = resp.status();
            let text = resp.text().await.unwrap_or_default();
            anyhow::bail!("Qdrant upsert failed ({status}): {text}");
        }

        Ok(())
    }

    async fn recall_for_agents(
        &self,
        allowed_agent_ids: &[&str],
        query: &str,
        limit: usize,
        session_id: Option<&str>,
        since: Option<&str>,
        until: Option<&str>,
    ) -> Result<Vec<MemoryEntry>> {
        // Empty allowlist = no agent filter (matches the wrapper's
        // semantics; see the SQL backends).
        if allowed_agent_ids.is_empty() {
            return self.recall(query, limit, session_id, since, until).await;

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Match dimensions (GET /collections/{c} -> vectors.size vs provider dims); export memories, recreate the collection under a new name, restart to rebuild, re-embed
  2. 413: store smaller content (split long documents before store)
  3. 5xx: check Qdrant memory/optimizer settings and retry once healthy; make retries idempotent by keying points on a content hash if you control the writer
  4. 404: recreate the collection and restart the process
Defensive patterns

Strategy: validation

Validate before calling

// Verify dims match before write-heavy flows
let col: serde_json::Value = client.get(format!("{url}/collections/{c}")).send().await?.json().await?;
let size = col.pointer("/result/config/params/vectors/size").and_then(|v| v.as_u64());
anyhow::ensure!(size == Some(embedder_dims as u64), "collection dims {size:?} != embedder {embedder_dims}");

Type guard

fn is_qdrant_upsert_error(e: &anyhow::Error) -> bool {
    e.to_string().starts_with("Qdrant upsert failed")
}

Try / catch

// store() generates a fresh UUID per call, so dedupe instead of blind retry
match memory.store(k, c).await {
    Err(e) if is_qdrant_upsert_error(&e) => { memory.forget(k).await.ok(); memory.store(k, c).await }
    other => other,
}

Prevention

When it happens

Trigger: 400 'vector dimension mismatch' (collection built for a different embedding model); 404 collection dropped after init; 413 oversized payload; 5xx under memory pressure or OOM during indexing.

Common situations: Changed embedding provider (or model version) without recreating the collection — the classic cause; extremely large memory contents exceeding Qdrant limits; Qdrant OOM while indexing a burst of writes.

Related errors


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