Repository navigation
Description
buzz-acp: owner-only sibling gate caches a transport failure as "not a sibling", permanently blacklisting a teammate bot until process restart
Summary
In owner-only respond mode, an inbound event from a sibling agent (a bot proving the same owner via its NIP-OA auth tag) is validated by querying the author's kind:0 profile over REST. When that /query times out or errors, the check fails closed and treats the author as not-a-sibling. That negative result is cached for the process lifetime, so a single transient network blip permanently blacklists a teammate bot: every subsequent message it sends is silently dropped by the inbound author gate until the agent process is restarted.
This is observable as "a bot answers my (owner) messages instantly but ignores all teammate-bot messages for hours," then starts working again after a restart.
Repro / evidence
Setup: multiple managed agents on one box, all owner-only respond mode, all owned by the same owner (so they are siblings to each other). A teammate bot triggers the agent (e.g. @ in the channel).
- Agent process restarts; at ~13s after launch it logs a
POST /query network error(transient). - From that point the agent silently ignores every subsequent event from that sibling bot (8.5h in the observed case), while remaining fully responsive to the owner.
- Owner messages still wake it instantly because they take the
author == my_ownershort-circuit and never touch REST/cache. - Restarting the agent process clears the blacklist (the sibling is immediately accepted again).
Root cause (with source refs)
All in block/buzz crates/buzz-acp/src/lib.rs.
owner-onlyreviews siblings:RespondTo::OwnerOnly => is_owner_or_sibling(author, owner_cache, rest_client)— andis_owner_or_siblingcallscheck_sibling_via_profilefor an unknown author. (searchis_owner_or_sibling+RespondTo::OwnerOnly)- Fail-closed:
check_sibling_via_profilereturnsfalseon any/querytimeout or error:let resp = match tokio::time::timeout(..., rest_client.query(&[filter])).await { Ok(Ok(v)) => v, _ => return false, // timeout or error — fail closed };
- Negative result is cached unconditionally, treating a transport error as an attestation:
and
let is_sibling = check_sibling_via_profile(author, my_owner, rest_client).await; owner_cache.cache_sibling(author.to_string(), is_sibling); // cached regardless of success/failure
is_known_siblingreturns the cached value (includingfalse) on all later calls.OwnerCachedocuments the cache as process-lifetime with no TTL (attestations are immutable, capped only by a 256-entry wholesale clear). - The drop is silent: the gate drops the event with
which is invisible at default INFO level.
tracing::debug!("inbound author gate — dropping event");
A transport error / timeout is not an attestation. Caching it as a permanent "not a sibling" conflates "we could not verify" (transient, should be retried) with "verified not a sibling" (durable). The comment claiming attestations are immutable is the mismatch: only successful verification results are attestations.
Suggested fix
- In
is_owner_or_sibling, write toOwnerCacheonly when the sibling verification succeeded; on a transport failure, do not cache (or use a short TTL), so the next message from the same author retries the check instead of being dropped forever. - Upgrade the gate's drop log from
debug!towarn!so these drops are observable. - Add a regression test: after a
/querynetwork failure for an author, the next message from the same author re-runs verification rather than short-circuiting to the cached negative.
Workaround
Broad enough: in the affected agent, use allowlist respond mode with the teammate pubkeys added (allowlist lookup is an in-process set check, no REST), or restart the agent process to clear the stale cached negative.
Activity
jaozolins commentedon Aug 10, 2026
Self-hosting multiple agents on one VPS (same owner, owner-only respond).
We’ve seen the same pattern: after a transient network blip, one agent still answers the human owner fine but stops reacting to teammate agents until the process is restarted. Looks “broken” for hours with no obvious error in the UI.
Caching a failed /query as permanent “not a sibling” matches what we observe. Not caching transport failures (or short TTL + warn log) would help a lot for multi-agent rooms.
cristiansotogarciaxatech commentedon Aug 13, 2026
Independent reproduction on a different platform (Windows 11, Buzz Desktop 0.5.10), and a note on why the obvious workaround doesn't help.
Confirming this exact failure mode on a separate deployment. Setup matches yours: ~8 managed agents on one box, all
respond_to=owner-only, all owned by the same owner (so they're NIP-OA siblings to each other), runtimeclaude/codex.Incident (2026-08-13, shared project channel):
@-mentioned agent Elbito (sibling bot).That asymmetry is the tell you describe: the owner message takes the
author == my_ownershort-circuit and never touches the REST/kind:0path, while the sibling bot stays blacklisted by the poisoned negative cache. Owner-instant + teammate-bot-silent-until-something-resets is exactly the signature.Frequency: intermittent — most agent↔agent mentions land fine; occasionally one goes "eyes-but-silent" and only clears on a re-trigger from the owner or a process restart. Consistent with a single transient
POST /queryblip poisoning the per-process sibling cache for that author.Extra UX detail worth capturing: because presence still reads "online" and an "eyes" reaction can still be applied to the dropped event, the blacklist is indistinguishable from a healthy agent to everyone in the channel. Humans read it as a hang and manually re-ping — there's no surfaced signal (log/reaction/status) that the inbound author gate dropped the event.
Why the config workaround doesn't rescue this today: the natural escape — set
respond_to=anyoneso there's no sibling gate at all — doesn't stick. Desktop's respond_to Save doesn't reach the running harness (#2501, #4487): the card can readanyonewhilebuzz-acp starting:logsowner-only, and every Desktop restart re-inheritsowner-onlyfrom disk. So agents keep restarting back into the exact mode that triggers this bug, and there's no owner-facing way out of it. Fixing #5450 (don't cache a negative on transport error; or short TTL + revalidate; or trust the event's NIP-OA auth tag for a proven-same-owner sibling without a REST round-trip) is what actually unblocks multi-agent teams, because the workaround path is itself broken.Happy to pull
buzz-acplogs (-32603/POST /query network error/author_allowed) from the affected window if useful.docs: prove the sibling gate instead of quoting the code comment
fix(buzz-acp): bound sibling-gate lookup failures instead of caching …
fix(buzz-acp): bound sibling-gate lookup failures instead of caching …