Conversation
…quired 403
On a self-hosted relay with BUZZ_REQUIRE_RELAY_MEMBERSHIP=true, the
automatic relay_members grant for a brand-new agent pubkey lands
asynchronously — typically within ~1s of the first connection attempt.
The desktop client's initial kind:0 profile publish therefore races the
grant, and a closed relay rejects the first POST /events with a 403
{"error":"relay_membership_required","message":"You must be a
relay member to access this relay"} even though the same publish
succeeds moments later. The customer-visible result is the
"Could not sync the agent's profile metadata: relay returned 403"
dialog on the happy-path Create Agent flow.
Make sync_managed_agent_profile retry the POST exactly once after a
500ms delay when (and only when) the relay returned 403 with
relay_membership_required in the body, and surface the original
error unchanged for all other failure shapes. One attempt is enough —
if the grant still hasn't landed after ~500ms the pod's membership
backfill is genuinely wedged and a retry loop won't help.
Receipts:
- Fixes block#3888.
- 4 new unit tests on the error-summarize helper cover the
membership-body, error-only, non-JSON, and empty-body shapes.
- Existing egress_guard boundary test
(boundary_sync_managed_agent_profile_blocks_ncryptsec) still passes.
- cargo check / cargo clippy -D warnings / cargo test --lib targeted
(summarize_relay 4/4 + egress_guard 1/1) all green on
buzz-desktop v0.5.3 (Tauri v2, Rust 1.x).
Signed-off-by: Sarthak Singh <sarthak.singh@juspay.in>
|
Hit this again today on my self-hosted instance — a freshly created agent got |
|
@uwebeyond thank you — that repro changes the picture. "100% reproducible for new agents while existing ones keep working" is a much stronger signal than the intermittent flake this PR was written against, and it points at the same root: the membership grant hasn't landed by the time the first profile sync fires, and without the retry the agent never recovers. If you're able to test a build, that would genuinely help the review: the branch is Sorry it's been sitting; your production repro is exactly the kind of confirmation that helps it move. |
Summary
Fixes #3888.
On a self-hosted relay with
BUZZ_REQUIRE_RELAY_MEMBERSHIP=true, the automaticrelay_membersgrant for a brand-new agent pubkey lands asynchronously — typically within ~1 s of the first connection attempt. The desktop client's initial kind:0 profile publish therefore races the grant, and a closed relay rejects the firstPOST /eventswith a 403{"error":"relay_membership_required","message":"You must be a relay member to access this relay"}even though the same publish succeeds moments later.The reporter verified directly against the relay's Postgres that:
relay_membersrow for the same pubkey is inserted at T+~130 ms–1 s,The visible result on the happy-path Create Agent flow is the
"Could not sync the agent's profile metadata: relay returned 403 Forbidden: You must be a relay member to access this relay"dialog, even though the agent was created and (by the time the user reads the dialog) is already a relay member.Change
sync_managed_agent_profilenow retries the POST exactly once — after a 500 ms delay — when (and only when) the relay returned a 403 whose body containsrelay_membership_required. All other 4xx/5xx failures surface the original error unchanged, and a fully denied (never-member) relay still errors exactly as before, just ~500 ms slower on the first attempt.One retry is enough: if the grant still hasn't landed after ~500 ms, the pod's membership backfill is genuinely wedged and a retry loop won't help. This is a small, deliberate defense against a specific, real race the reporter reproduced twice — not a general retry policy.
Because the fix lives inside
sync_managed_agent_profile, every caller benefits:commands/agents.rs(the reported path),commands/agent_models.rs,commands/personas/snapshot/import.rs,commands/personas/update.rs,commands/agents_profile.rs,commands/team_snapshot.rs— no caller changes needed.The body-builder also gained a small
summarize_relay_errorhelper that mirrors the "real relay error" branch of the existingrelay_error_message, but takes a pre-read body string. That's just so the new retry path can consume the response body once and reuse the same message format;relay_error_message(which still owns itsreqwest::Response) is untouched.Test plan
summarize_relay_errorhelper: membership-required JSON, error-only JSON, non-JSON body, and empty body shapes.egress_guard::tests::boundary_sync_managed_agent_profile_blocks_ncryptsecboundary test (which callssync_managed_agent_profiledirectly with a fake secret-bearing payload) still passes — the function still rejectsncryptsecbody bytes before any network I/O.cargo check --lib·cargo clippy --lib --all-targets -- -D warnings·cargo test --lib summarize_relayall green onbuzz-desktopv0.5.3 (Tauri v2).Not end-to-end-verified against a real self-hosted closed relay (would require a two-pod relay + a
BUZZ_REQUIRE_RELAY_MEMBERSHIP=trueconfig + a frozen Postgres), but the behavior change is fully contained in an existing async fn whose error / success surface is unit-tested, and the new retry is gated on the exact 403 body shape the relay emits (crates/buzz-relay/src/api/mod.rs:136).Oval-known risks
0..2and breaks onis_membership_403 && attempt == 0continuing once, otherwise returning the final error.