Skip to content

fix(desktop): retry agent profile sync once after relay_membership_required 403 - #4088

Open
iroiro147 wants to merge 1 commit into
block:mainfrom
iroiro147:claude/issue3888-membership-retry-20260801
Open

fix(desktop): retry agent profile sync once after relay_membership_required 403#4088
iroiro147 wants to merge 1 commit into
block:mainfrom
iroiro147:claude/issue3888-membership-retry-20260801

Conversation

@iroiro147

Copy link
Copy Markdown

Summary

Fixes #3888.

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 ~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 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 reporter verified directly against the relay's Postgres that:

  • the 403 lands at T,
  • the relay_members row for the same pubkey is inserted at T+~130 ms–1 s,
  • the client never retried the kind:0 publish, even minutes later.

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_profile now retries the POST exactly once — after a 500 ms delay — when (and only when) the relay returned a 403 whose body contains relay_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_error helper that mirrors the "real relay error" branch of the existing relay_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 its reqwest::Response) is untouched.

Test plan

  • 4 new unit tests cover the new summarize_relay_error helper: membership-required JSON, error-only JSON, non-JSON body, and empty body shapes.
  • The existing egress_guard::tests::boundary_sync_managed_agent_profile_blocks_ncryptsec boundary test (which calls sync_managed_agent_profile directly with a fake secret-bearing payload) still passes — the function still rejects ncryptsec body bytes before any network I/O.
  • cargo check --lib · cargo clippy --lib --all-targets -- -D warnings · cargo test --lib summarize_relay all green on buzz-desktop v0.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=true config + 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

  • One extra retry when the relay truly never grants membership (instead of failing immediately). Bounded by the 500 ms sleep + the single retry — worst case adds ~500 ms + one HTTP round-trip on a first-publish failure that already required a relay round-trip. The reporter's repro (grant lands in 130 ms–1 s) makes 500 ms the right ballpark; matching the reporter's stated workaround cadence.
  • No retry-loop risk: the loop is bounded to 0..2 and breaks on is_membership_403 && attempt == 0 continuing once, otherwise returning the final error.

…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Self-hosted relay: agent's first profile publish (kind:0) gets 403 relay_membership_required, client never retries after membership resolves

1 participant