fix(broker): bound and retry the Relaycast startup handshake
Summary
Fixes the flaky failure in the publish workflow's macOS standalone lifecycle smoke (verify-standalone-macos), which intermittently failed with:
Broker process exited with code null during initial handshake → Broker startup readiness line missing or duplicated: expected 1, got 0
Root cause (a hang, not a crash): connect_relay performs the mandatory Relaycast handshake via the relaycast SDK's workspace bootstrap (create_workspace / agent registration). Those bootstrap calls build a timeout-less reqwest client, so when the connection to the relay backend stalls, startup_session_set_with_options hangs indefinitely. The broker never prints Broker started., and because the CLI's node up is still within its 45s handshake budget, the standalone smoke's own sleep 8 && down --force teardown then kills the still-starting broker — surfacing to the SDK as an opaque code null (signal) exit "during initial handshake".
code null (killed by signal) rather than a clean error exit (1) or a Rust panic (101) is what pointed at an external reap of a hung process rather than a code fault. This was confirmed flaky: the same run/commit was re-run and passed.
Fix: Wrap startup_session_set_with_options in a per-attempt tokio::time::timeout with bounded retry + exponential backoff. A stalled attempt now aborts fast and retries in-process, well within the SDK's 45s startup budget, instead of hanging until something reaps the process. Terminal failures/timeouts surface as clean, contextual errors instead of a silent hang.
- Defaults: 5s per attempt, 4 attempts, 250ms → 2s backoff (worst case ~22s < 45s SDK budget).
- Overridable via
AGENT_RELAY_HANDSHAKE_TIMEOUT_MSandAGENT_RELAY_HANDSHAKE_ATTEMPTS. - Startup-debug tracing logs each attempt/timeout/retry for future diagnosis.
Files: crates/broker/src/runtime/session.rs (+ CHANGELOG.md).
Follow-ups (not in this PR)
- The upstream defect lives in
relaycastsdk-rust:create_workspace/lookup_workspaceshould build a client with an explicit timeout (the authenticated client already uses 30s). Fixing that needs a crate release + version bump. - The "standalone" smoke isn't truly standalone — it depends on production Relaycast reachability and mints a throwaway workspace each run. Pointing it at a local
RELAY_BASE_URLstub would remove the network dependency entirely.
Test Plan
- Tests added/updated — unit tests for the
AGENT_RELAY_HANDSHAKE_TIMEOUT_MS/AGENT_RELAY_HANDSHAKE_ATTEMPTSenv overrides (default/invalid/zero/valid) - Manual testing completed —
cargo check,cargo clippy(clean),cargo fmt, andcargo test -p agent-relay-broker --lib handshake(2 passed) on the broker crate
Screenshots
🤖 Generated with Claude Code
https://claude.ai/code/session_01WzkvFAUELJgDQgZ3pdoRTP
Generated by Claude Code