block/buzz · critical · anyhow::Error

mesh ready-registry publish failed

Error message

mesh ready-registry publish failed: {e}

What it means

mesh_boot publishes the relay's attested ready-record to the Redis ready-registry as part of boot; the source comment is explicit — if Redis cannot take the record, peers can never discover this instance, so boot fails loudly instead of running undiscoverable. Any error from registry.publish_ready (Redis connection, TLS, auth, serialization of the attested record) is wrapped as 'mesh ready-registry publish failed' and aborts relay startup when mesh is enabled.

Solutions

  1. Verify Redis reachability from the relay host: `redis-cli -u <url> PING` (use rediss:// and --tls for TLS endpoints).
  2. Fix the URL scheme: rediss:// for TLS-encrypted Redis (ElastiCache), redis:// only for plaintext.
  3. Check credentials/ACLs: the relay's Redis user needs SET/PUBLISH on the ready-registry keys.
  4. Open network egress (security groups / NetworkPolicies) to the Redis port.
  5. If you only want single-instance behavior, set BUZZ_MESH to a value other than 'on' — mesh_boot then skips registry publish entirely.

Example fix

# before
BUZZ_MESH=on
BUZZ_REDIS_URL=redis://mesh-redis.internal:6379  # against TLS-enabled ElastiCache

# after
BUZZ_MESH=on
BUZZ_REDIS_URL=rediss://mesh-redis.internal:6379
Defensive patterns

Strategy: retry

Validate before calling

# preflight from the relay host before enabling mesh
redis-cli --tls -u "$BUZZ_REDIS_URL" PING   # expect PONG

Try / catch

// at boot: retry the publish briefly before failing fast, so a restarting Redis
// does not take the relay down
let mut attempt = 0;
loop {
    match registry.publish_ready(&ready_record).await {
        Ok(()) => break,
        Err(e) if attempt < 5 => { attempt += 1; tokio::time::sleep(Duration::from_secs(2)).await; }
        Err(e) => return Err(anyhow::anyhow!("mesh ready-registry publish failed: {e}")),
    }
}

Prevention

When it happens

Trigger: registry.publish_ready(&ready_record) at crates/buzz-relay/src/mesh_boot.rs:458-462 fails when: Redis is unreachable (wrong host/port in the mesh registry config), rediss:// TLS cert validation fails (ElastiCache), auth credentials are wrong (NOAUTH/WRONGPASS), the key is under a write-protected policy, or the record exceeds Redis protocol limits.

Common situations: Enabling BUZZ_MESH=on locally without the staging Redis running or reachable; ElastiCache in-transit encryption enabled but the relay's Redis URL uses redis:// instead of rediss://; rotated Redis credentials not deployed to the K8s secret; security-group rules blocking the node's egress to Redis port 6379.

Related errors


AI-assisted analysis of block/buzz@934f3325c3 (2026-08-20). Data as JSON: /api/errors/86349a1b6a92db26. Report an issue: GitHub.

Appendix: source

Thrown at crates/buzz-relay/src/mesh_boot.rs:462

    // foreign and rejected (Wren's review — possession is not authorization).
    let membership = MeshMembership::new(local_record)
        .with_expected_relay_pubkey(relay_keypair.public_key().to_hex());

    let registry = ReadyRegistry::new(redis_pool.clone(), config.mesh.registry_refresh);
    let ready_record = ReadyRecord::new(
        runtime_id,
        relay_keypair,
        addrs,
        PROTO_VERSION,
        capabilities(),
    );

    // First publish is part of boot: if Redis can't take the attested record,
    // peers can never find us — fail loudly now, not quietly forever.
    registry
        .publish_ready(&ready_record)
        .await
        .map_err(|e| anyhow::anyhow!("mesh ready-registry publish failed: {e}"))?;
    tracing::info!(runtime_id = %runtime_id, "mesh ready record published");

    // Readiness-gated heartbeat: publishes while the relay would pass
    // readiness, clears the record on ready→not-ready and on shutdown.
    let hb_flag = Arc::clone(&shutting_down);
    buzz_relay_mesh::runtime::spawn_registry_heartbeat(
        registry.clone(),
        ready_record,
        Arc::new(move || !hb_flag.load(Ordering::Relaxed)),
    );

    let runtime = MeshRuntime::start(endpoint, membership, Some(registry));
    let owners = Arc::new(crate::audio::join::HuddleOwnerRegistry::new());
    // Dial seed peers now rather than waiting for the first reconcile tick.
    runtime.reconcile_now().await;

    // Drain watcher: SIGTERM flips `shutting_down`; gossip `draining=true` so
    // peers stop routing new sessions here, then actively drain locally-owned

View on GitHub (pinned to 934f3325c3)