block/buzz · error

thread metadata lookup failed: {e}

Error message

thread metadata lookup failed: {e}

What it means

Before deleting the target event, `run_atomic_mutation` calls `get_thread_metadata_by_event` to look up the thread's parent/root ids (used to update thread counters), and that query failed with a database error, wrapped as `thread metadata lookup failed: {e}`. This is a DB-layer failure, not a business-logic rejection — the delete itself never ran.

Source

Thrown at crates/buzz-relay/src/handlers/report_resolution.rs:757

                .await
                .map_err(|e| anyhow::anyhow!("kick failed: {e}"))?
            {
                buzz_db::relay_admin_actions::KickWithMarkerResult::Removed => Ok(true),
                buzz_db::relay_admin_actions::KickWithMarkerResult::AlreadyMarked => Ok(false),
                buzz_db::relay_admin_actions::KickWithMarkerResult::AlreadyGone => Err(
                    anyhow::anyhow!("kick target was already absent before this action"),
                ),
            }
        }
        "delete" => {
            let target = ctx
                .target_event_id
                .ok_or_else(|| anyhow::anyhow!("delete requires target_event_id"))?;
            let meta = state
                .db
                .get_thread_metadata_by_event(ctx.community_id, target)
                .await
                .map_err(|e| anyhow::anyhow!("thread metadata lookup failed: {e}"))?;
            let parent_id = meta.as_ref().and_then(|m| m.parent_event_id.clone());
            let root_id = meta.as_ref().and_then(|m| m.root_event_id.clone());
            state
                .db
                .execute_delete_with_marker(
                    action_id,
                    lease_token,
                    ctx.community_id,
                    target,
                    parent_id.as_deref(),
                    root_id.as_deref(),
                )
                .await
                .map_err(|e| anyhow::anyhow!("delete failed: {e}"))
        }
        other => Err(anyhow::anyhow!("unexpected enforcement action: {other}")),
    };

View on GitHub (pinned to eed74bde2f)

Solutions

  1. Read the wrapped `{e}` in relay logs to identify the exact Postgres error (connection, timeout, relation-not-found).
  2. For relation/column errors: run pending migrations and restart the relay so schema matches the binary.
  3. For timeouts/pool exhaustion: raise pool size or statement timeout, and retry — enforcement is idempotent and re-drivable.
  4. Confirm the event id passed to `get_thread_metadata_by_event` is well-formed (32-byte binary) and not truncated upstream.

Example fix

// before
let meta = state.db.get_thread_metadata_by_event(ctx.community_id, target)
    .await
    .map_err(|e| anyhow::anyhow!("thread metadata lookup failed: {e}"))?;
// after
let meta = match state.db.get_thread_metadata_by_event(ctx.community_id, target).await {
    Ok(m) => m,
    Err(e) if is_transient(&e) => {
        tokio::time::sleep(Duration::from_millis(200)).await;
        state.db.get_thread_metadata_by_event(ctx.community_id, target).await
            .map_err(|e| anyhow::anyhow!("thread metadata lookup failed: {e}"))?
    }
    Err(e) => return Err(anyhow::anyhow!("thread metadata lookup failed: {e}")),
};
Defensive patterns

Strategy: retry

Validate before calling

// verify the target event exists before driving a delete
let exists = state.db.event_exists(community_id, target_event_id).await?;
anyhow::ensure!(exists, "target event not found in community; skip delete");

Type guard

fn is_transient_db_err(e: &anyhow::Error) -> bool {
    let s = e.to_string();
    s.contains("connection") || s.contains("timeout") || s.contains("pool")
}

Try / catch

match run_atomic_mutation(state, action_id, lease_token, &ctx).await {
    Err(e) if is_transient_db_err(&e) => schedule_retry(action_id, backoff()),
    Err(e) if e.to_string().contains("thread metadata lookup failed") => {
        alert_schema_drift_or_db_health(action_id, e);
    }
    other => other?,
}

Prevention

When it happens

Trigger: 1) Postgres connection failure, pool exhaustion, or statement timeout while reading thread metadata. 2) Schema drift: the thread-metadata table/columns expected by the query are missing (failed migration). 3) Malformed `target_event_id` causing a query/decode error at the DB layer rather than a clean `None`.

Common situations: Relay under heavy load with a saturated connection pool; a half-applied migration after upgrading the relay binary; disk/network issues between relay and Postgres in containerized deployments (e.g. the staging Kubernetes setup).

Related errors


AI-assisted analysis of block/buzz@eed74bde2f (2026-08-30). Data as JSON: /api/errors/e91dbe4a1d0c2d03. Report an issue: GitHub.