mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
## Problem On SIGTERM the relay sends every live WebSocket a **1012 Service Restart** close frame via `ConnectionManager::drain_all()` — all in the same instant (`main.rs` shutdown task → `state.rs::drain_all`). On a pod holding thousands of sessions, that makes every client reconnect simultaneously: the thundering-herd reconnect behind the DB pool-timeout bursts observed on each rolling deploy. Client-side jitter can't fix this — the desktop client *resets* its backoff to base on a 1012 and reconnects with only ±25% jitter (`relayClientSession.ts`), so the spread has to come from the server. ## Change Add `BUZZ_DRAIN_JITTER_MS` (default `0` = unchanged behavior). The two paths are kept **deliberately separate** so the default is byte-for-byte the previously shipped shutdown: - **Jitter off (`0`/unset, the default):** the original synchronous, all-at-once `drain_all()` runs unchanged — queue the 1012 on each connection's control channel, cancel, return. No new machinery on the default path. - **Jitter on (`> 0`):** a separate async `drain_all_jittered(jitter_ms)` spreads each connection's restart close over an independent uniform delay in **`[1, jitter_ms]`**. Each delayed close travels a dedicated `RestartClose` channel; the writer flushes the 1012 frame and **acknowledges the flush over a oneshot**, so drain waits for confirmed delivery (up to `RESTART_CLOSE_ACK_TIMEOUT` = 5s) rather than assuming it, falling back to cancellation if the channel is full/closed or the ack times out. The drain future is **owned and awaited** by the shutdown task, and the 30s hard-drain backstop is aborted only after a clean drain — so a clean roll exits `0`. The two methods can be unified and the old one dropped later once the jittered path is proven for all cases. - **`config.rs`** — `drain_jitter_ms`: non-negative parse, clamped to `MAX_DRAIN_JITTER_MS` = **20s** (leaving 10s of the 30s budget for flush). Junk fails loudly at startup; **empty/whitespace-only is treated as unset (jitter off)** so a `BUZZ_DRAIN_JITTER_MS=""` kill switch does not crashloop the relay (matches the sibling env vars in this file). - **`state.rs`** — `drain_all()` (unchanged synchronous default) + `drain_all_jittered()` (jittered + flush-ack). Both set the sticky `draining` flag before the first await. A registration that lands mid-shutdown always self-signals via the **immediate** control-frame + cancel path — jitter smears already-established sockets, not late arrivals. - **`main.rs`** — shutdown task dispatches: `drain_jitter_ms == 0` → `drain_all()`, else `drain_all_jittered(...).await`. ## Safety - **Default off is the currently-committed path.** With jitter unset/0 the shutdown runs the original synchronous `drain_all()` — no restart channel, no ack wait. Safe to deploy dark and dial up. - **Shutdown-boundary race preserved.** Sticky flag set before any await; a late registration self-signals its close with no jitter. - **Owned + backstopped.** The jittered drain future is awaited; the 30s hard-drain `process::exit(1)` remains the ceiling. `MAX_DRAIN_JITTER_MS` (20s) + `RESTART_CLOSE_ACK_TIMEOUT` (5s) = 25s, inside the 30s budget; 5s pre-sleep + 25s = 30s against `terminationGracePeriodSeconds: 60`. ## Known behavior to note (not a blocker, flagged from review) On a **successful** flush the jittered path deliberately does not cancel the connection token — teardown then depends on the client echoing our Close, or on process exit. Compliant clients echo; a silent client rides to the 30s hard exit. The default (jitter-off) path cancels deterministically as before. ## Tests - `config::tests::drain_jitter_defaults_off_and_rejects_junk` — default off, `20000`, clamp `60000`→`20000`, explicit `0`, junk `"soon"` fails, **empty `""` and whitespace-only treated as off**. - `state::tests::drain_all_is_immediate` — default path queues frame + cancels synchronously. - `state::tests::drain_all_sends_restart_close_and_cancels_every_conn`, `drain_all_full_control_buffer_still_cancels`, `register_after_drain_self_signals_restart_close_and_cancel`. - `state::tests::drain_all_jittered_defers_close_until_within_jitter_window` (paused time). - `state::tests::drain_all_jittered_waits_for_writer_acknowledgement_without_cancelling`. - `state::tests::drain_all_jittered_cancels_when_restart_channel_is_full_or_closed`. - `state::tests::drain_all_jittered_cancels_when_flush_ack_times_out` (paused time — the 5s ack-timeout fallback). Validation at `46c690940`: `cargo fmt -p buzz-relay --check`, `cargo clippy -p buzz-relay --all-targets -- -D warnings`, and the drain/config unit suite all clean. Local live SIGTERM test with a real relay process + 200 NIP-42-authenticated sockets — see the PR comment for the before/after distribution and exit codes. ## Rollout Ship with default `0`, then set `BUZZ_DRAIN_JITTER_MS` (e.g. 10000–20000) on bb-block first, watch the roll-window pool-timeout metric, then bb-public. `""` is a safe kill switch. Complements the preStop `sleep` (stops routing before close). --------- Signed-off-by: npub1srl70fhzyu3fsnahl06vw2czvqc2w3ds37hyzvjnk8ve8f03ngcqg9le2w <80ffe7a6e22722984fb7fbf4c72b026030a745b08fae413253b1d993a5f19a30@buzz.block.builderlab.xyz> Signed-off-by: npub128x7j3pwgm4vs8yra3c42fcgcwcvh94g3luwzkqa376du2q6l0esqcrwch <51cde9442e46eac81c83ec71552708c3b0cb96a88ff8e1581d8fb4de281afbf3@buzz.block.builderlab.xyz> Signed-off-by: Brad Seiler <seiler@squareup.com> Co-authored-by: npub1srl70fhzyu3fsnahl06vw2czvqc2w3ds37hyzvjnk8ve8f03ngcqg9le2w <80ffe7a6e22722984fb7fbf4c72b026030a745b08fae413253b1d993a5f19a30@buzz.block.builderlab.xyz> Co-authored-by: npub128x7j3pwgm4vs8yra3c42fcgcwcvh94g3luwzkqa376du2q6l0esqcrwch <51cde9442e46eac81c83ec71552708c3b0cb96a88ff8e1581d8fb4de281afbf3@buzz.block.builderlab.xyz>
313 lines
16 KiB
Markdown
313 lines
16 KiB
Markdown
# Testing
|
||
|
||
## Automated Tests
|
||
|
||
```bash
|
||
just test-unit # unit tests — no infrastructure needed
|
||
just test # unit + integration (starts Docker if needed)
|
||
```
|
||
|
||
`just test` runs unit tests plus integration tests against Postgres and Redis
|
||
(started automatically if not already running). Neither task runs the E2E suites in
|
||
`buzz-test-client` — those are marked `#[ignore]` and require a running relay:
|
||
|
||
```bash
|
||
# Start a relay first (see below), then:
|
||
cargo test -p buzz-test-client -- --ignored
|
||
```
|
||
|
||
---
|
||
|
||
## Live Local Relay
|
||
|
||
The fastest way to exercise the relay end-to-end is to build the release
|
||
binaries once, run `buzz-relay`, and drive it with the `buzz` CLI. The
|
||
CLI signs every request with NIP-98, so you don't need `nak` or hand-rolled
|
||
`curl`.
|
||
|
||
### 1. Setup
|
||
|
||
```bash
|
||
. ./bin/activate-hermit # activate pinned toolchain
|
||
cp .env.example .env # one-time
|
||
just setup # start Docker services, run migrations
|
||
```
|
||
|
||
> **Already running Buzz Desktop?** Desktop uses the same Docker container
|
||
> names (`buzz-postgres`, `buzz-redis`) and the same
|
||
> default ports (`:5432`, `:6379`). `just setup` will reuse those
|
||
> services, so **your test relay writes into Desktop's database**. That's
|
||
> fine for read/write smoke tests, but: `just reset` wipes Desktop's data
|
||
> along with yours. If you need isolation, stop Desktop first or run the
|
||
> dev stack on a different Compose project
|
||
> (`COMPOSE_PROJECT_NAME=buzz-dev docker compose …`).
|
||
|
||
`just reset` wipes all local data and starts over — **including Buzz
|
||
Desktop's data** if its services are sharing your dev stack (see callout
|
||
above).
|
||
|
||
> **Heads up — scrub stale env first.** If your shell inherits any of
|
||
> `BUZZ_AUTH_TAG`, `BUZZ_RELAY_URL`, or `BUZZ_PRIVATE_KEY` from a
|
||
> prior session (or a staging config), `unset` them before continuing.
|
||
> A stale `BUZZ_AUTH_TAG` fails the **local dev relay** with
|
||
> `auth_error: signature verification failed` on the first CLI write —
|
||
> it is *not* tolerated.
|
||
> ```bash
|
||
> unset BUZZ_AUTH_TAG BUZZ_RELAY_URL BUZZ_PRIVATE_KEY
|
||
> ```
|
||
|
||
### 2. Build the binaries
|
||
|
||
```bash
|
||
cargo build --release -p buzz-relay -p buzz-cli -p buzz-admin
|
||
export PATH="$PWD/target/release:$PATH"
|
||
```
|
||
|
||
Rebuild after any code change — the steps below use the release binaries.
|
||
|
||
### 3. Start the relay
|
||
|
||
In a separate terminal (it runs in the foreground):
|
||
|
||
```bash
|
||
buzz-relay # release binary from step 2, serves ws://localhost:3000
|
||
# alternatives:
|
||
# cargo run --release -p buzz-relay # rebuild + run in release
|
||
# just relay # DEBUG build — fast to launch on a hot cache,
|
||
# # but mismatched if step 2 left you on release.
|
||
# # Use `just relay-release` if you want the recipe.
|
||
```
|
||
|
||
Verify it's up (back in your working terminal):
|
||
|
||
```bash
|
||
curl -s http://localhost:3000/health # → ok
|
||
curl -s http://localhost:8080/_readiness # → {"status":"ready"}
|
||
```
|
||
|
||
> Health/readiness/liveness live on a **separate port** (default `8080`,
|
||
> `BUZZ_HEALTH_PORT`) so K8s probes bypass auth middleware. The main app
|
||
> port also exposes `/health` for convenience.
|
||
|
||
The relay starts in dev mode (`BUZZ_REQUIRE_AUTH_TOKEN=false`). The startup
|
||
log emits a WARN about this — that's expected for local testing. See the env
|
||
vars table at the bottom if you need to lock it down.
|
||
|
||
> **Already running Buzz Desktop (or another relay) on `:3000` / `:8080` /
|
||
> `:9102`?** Buzz binds three ports — main, health, metrics — and any of
|
||
> them can collide. Use a separate terminal per role and export the right
|
||
> vars in each:
|
||
>
|
||
> **In the relay terminal** (before launching `buzz-relay`):
|
||
> ```bash
|
||
> export BUZZ_BIND_ADDR=0.0.0.0:3030
|
||
> export BUZZ_HEALTH_PORT=8088
|
||
> export BUZZ_METRICS_PORT=9202
|
||
> export RELAY_URL=ws://localhost:3030 # advertised in NIP-42 challenges
|
||
> buzz-relay
|
||
> ```
|
||
>
|
||
> **In your working / CLI terminal** (for steps 4+ and the ACP harness):
|
||
> ```bash
|
||
> export BUZZ_RELAY_URL=http://localhost:3030 # CLI target
|
||
> # verify the relay on the overridden ports:
|
||
> curl -s http://localhost:3030/health # → ok
|
||
> curl -s http://localhost:8088/_readiness # → {"status":"ready"}
|
||
> ```
|
||
>
|
||
> Every snippet later in this doc shows the defaults. When you see
|
||
> `localhost:3000` / `:8080` in a code block, mentally substitute your
|
||
> overrides — or the CLI will end up talking to Buzz Desktop's relay.
|
||
|
||
> **Ignore `just setup`'s "Next steps" banner.** It still prints
|
||
> `just relay` (a debug build). Use `buzz-relay` from step 2 here —
|
||
> step 2 already built the release binary.
|
||
|
||
When you're done, stop the relay (Ctrl-C in its terminal). If it's
|
||
backgrounded or you lost the terminal: `pkill -f buzz-relay`. Leaving
|
||
it running will collide with the next reviewer who follows this doc on
|
||
the same machine.
|
||
|
||
### 4. Smoke test the CLI against the relay
|
||
|
||
End-to-end: generate an identity, create a channel, post a message, read it
|
||
back. This is the minimum sequence an agent needs to verify a local relay.
|
||
|
||
```bash
|
||
# Generate a keypair
|
||
GEN=$(buzz-admin generate-key)
|
||
export BUZZ_PRIVATE_KEY=$(echo "$GEN" | awk '/Secret key:/ {print $3}')
|
||
PUBKEY=$(echo "$GEN" | awk '/Public key:/ {print $3}')
|
||
echo "pubkey: $PUBKEY"
|
||
|
||
# Create a channel — the UUID is returned in the response
|
||
CHANNEL=$(buzz channels create --name "smoke-$$" --type stream --visibility open | jq -r '.channel_id')
|
||
echo "channel: $CHANNEL"
|
||
|
||
# Send a message and read it back
|
||
SEND=$(buzz messages send --channel "$CHANNEL" --content "hello from smoke test")
|
||
EVENT_ID=$(echo "$SEND" | jq -r '.event_id')
|
||
buzz messages get --channel "$CHANNEL" --limit 5 | jq .
|
||
|
||
# Fetch the reply chain for a specific message (empty array on a leaf — that's fine)
|
||
buzz messages thread --channel "$CHANNEL" --event "$EVENT_ID" | jq .
|
||
```
|
||
|
||
A successful run prints `{"event_id":"…","accepted":true,"message":""}` for
|
||
the send, and the message body in the `get` output. `thread` returns `[]`
|
||
for a leaf message — populated only after a reply comes in (see §5).
|
||
|
||
### 5. Going deeper
|
||
|
||
For full coverage of every CLI command (54 subcommands across 12 groups),
|
||
follow [`crates/buzz-cli/TESTING.md`](crates/buzz-cli/TESTING.md).
|
||
|
||
The relay's HTTP bridge accepts three endpoints — useful if you're testing
|
||
a client other than `buzz-cli`:
|
||
|
||
| Endpoint | Purpose |
|
||
|-----------------|------------------------------------|
|
||
| `POST /events` | Submit a signed Nostr event |
|
||
| `POST /query` | NIP-01 filter query (returns events) |
|
||
| `POST /count` | NIP-45 count query |
|
||
|
||
All three accept NIP-98 auth (recommended) or, in dev mode, an `X-Pubkey`
|
||
header fallback. There is no REST API for fetching message threads — use
|
||
`POST /query` with an `#e` filter, or `buzz messages thread`.
|
||
|
||
---
|
||
|
||
## ACP Harness (optional, end-to-end with a real agent)
|
||
|
||
`buzz-acp` connects an ACP-speaking agent (goose, codex, claude code,
|
||
buzz-agent) to the relay. The harness listens for events, drives the
|
||
agent over stdio, and the agent replies through MCP tools.
|
||
|
||
Minimum recipe — assumes the relay from step 3 is running and the channel
|
||
`$CHANNEL` from step 4 still exists. The agent identity must be **different**
|
||
from the sender identity (`BUZZ_ACP_RESPOND_TO=anyone` still skips events
|
||
the agent signed itself).
|
||
|
||
```bash
|
||
cargo build --release -p buzz-acp
|
||
export PATH="$PWD/target/release:$PATH"
|
||
|
||
# 1. Save your sender identity from step 4 — you'll need it to @mention the agent
|
||
SENDER_SK="$BUZZ_PRIVATE_KEY"
|
||
|
||
# 2. Mint a fresh agent identity and capture its pubkey
|
||
AGENT_GEN=$(buzz-admin generate-key)
|
||
AGENT_SK=$(echo "$AGENT_GEN" | awk '/Secret key:/ {print $3}')
|
||
AGENT_PUBKEY=$(echo "$AGENT_GEN" | awk '/Public key:/ {print $3}')
|
||
|
||
# 3. Add the agent as a member of $CHANNEL — still using the sender identity.
|
||
# Skip this and the agent boots to "discovered 0 channel(s) → agent will
|
||
# sit idle" and silently ignores every mention.
|
||
buzz channels add-member --channel "$CHANNEL" --pubkey "$AGENT_PUBKEY" --role member
|
||
|
||
# 4. Switch to the agent identity and start it.
|
||
# buzz-acp wants ws:// (not http://). If you set BUZZ_RELAY_URL to an
|
||
# http:// URL in step 3, set the ws:// equivalent here — same host/port.
|
||
export BUZZ_PRIVATE_KEY="$AGENT_SK"
|
||
export BUZZ_RELAY_URL=ws://localhost:3000 # match step 3 (e.g. ws://localhost:3030 if overridden)
|
||
export BUZZ_ACP_RESPOND_TO=anyone # default is owner-only; opens the gate for testing
|
||
# NIP-AE core-memory prompt injection is on by default; set BUZZ_ACP_NO_MEMORY=true to opt out.
|
||
export GOOSE_MODE=auto # must be 'auto' or goose hangs on prompts
|
||
|
||
buzz-acp # foreground; logs to stdout (run in a separate terminal)
|
||
|
||
# Optional: turn on per-turn tracing if the default log is too quiet.
|
||
# RUST_LOG=buzz_acp=debug buzz-acp
|
||
```
|
||
|
||
> **Using a different ACP agent?** The default recipe assumes `goose` is on
|
||
> `$PATH` and configured (`goose --version` should print). For codex / claude
|
||
> code / buzz-agent, set `BUZZ_ACP_AGENT_COMMAND` and `BUZZ_ACP_AGENT_ARGS`
|
||
> accordingly — see `crates/buzz-acp/README.md`. Without these, buzz-acp
|
||
> will fail to spawn the agent subprocess on startup.
|
||
|
||
If you started the agent before adding it to the channel, just run the
|
||
`add-member` afterwards — it picks up the membership notification live and
|
||
subscribes without restart (`membership notification: subscribing to new channel …`).
|
||
|
||
The justfile also ships `just goose key="$AGENT_NSEC"` (foreground) and
|
||
`just goose-bg key="$AGENT_NSEC"` (background screen session) which set the
|
||
same env. See `crates/buzz-acp/README.md` for parallel agents, heartbeats,
|
||
respond-to gates, and forum subscriptions.
|
||
|
||
To exercise deferred ACP startup, add `BUZZ_ACP_LAZY_POOL=true` before launching
|
||
`buzz-acp`. The harness should connect, authenticate, subscribe, and publish
|
||
online presence without starting the configured ACP child. The first accepted,
|
||
flushable mention should start exactly one child and then dispatch the queued
|
||
message. Automated coverage in `pool_lifecycle_state` pins single-wake,
|
||
retry/backoff, and stale-result behavior; it does not replace this real
|
||
relay/process smoke test.
|
||
|
||
Send the agent a task — switch your shell back to the **sender** identity
|
||
from step 4 and @mention the agent:
|
||
|
||
```bash
|
||
export BUZZ_PRIVATE_KEY=$SENDER_SK # the key from step 4
|
||
buzz messages send --channel "$CHANNEL" \
|
||
--content "Hey agent, reply PONG only."
|
||
|
||
# Wait 10–90s, then read the channel — the agent's reply is a kind:9 from
|
||
# AGENT_PUBKEY. The current ACP build is quiet on stdout during a turn, so
|
||
# `buzz messages get` is how you confirm it ran.
|
||
buzz messages get --channel "$CHANNEL" --limit 5 | jq '.[] | {pubkey, content}'
|
||
```
|
||
|
||
Replies are kind:9 in the same channel; `buzz messages thread --channel <id>
|
||
--event <event_id>` fetches the reply chain for a specific mention.
|
||
|
||
---
|
||
|
||
## Configuration reference
|
||
|
||
The relay reads all configuration from environment variables. Defaults work
|
||
out of the box with `just setup` or `just relay`. Common overrides:
|
||
|
||
| Variable | Default | Notes |
|
||
|-----------------------------------|-----------------------------|-------|
|
||
| `BUZZ_BIND_ADDR` | `0.0.0.0:3000` | Main app port |
|
||
| `BUZZ_HEALTH_PORT` | `8080` | `/_liveness`, `/_readiness` |
|
||
| `BUZZ_METRICS_PORT` | `9102` | Prometheus `/metrics` |
|
||
| `RELAY_URL` | `ws://localhost:3000` | Advertised in NIP-11 / NIP-42 challenges. **Note: no `BUZZ_` prefix.** |
|
||
| `DATABASE_URL` | `postgres://buzz:buzz_dev@localhost:5432/buzz` | |
|
||
| `REDIS_URL` | `redis://localhost:6379` | |
|
||
| `BUZZ_REQUIRE_AUTH_TOKEN` | `false` | When true, REST requires NIP-98 (no `X-Pubkey` fallback) |
|
||
| `BUZZ_REQUIRE_RELAY_MEMBERSHIP` | `false` | When true, only pubkeys in `relay_members` can connect |
|
||
| `BUZZ_REQUIRE_MEDIA_GET_AUTH` | `false` | When true, `GET`/`HEAD /media/*` require Blossom kind 24242 `t=get` auth plus relay membership. |
|
||
| `BUZZ_DRAIN_JITTER_MS` | `0` (off) | Per-connection upper bound, in ms, for the random delay before each live WebSocket gets its `1012 Service Restart` close on graceful shutdown. `0` closes every socket at once (the previous behavior). A positive value spreads closes uniformly over `[1, value]` ms to avoid a reconnect thundering herd on rolling deploys. Values above `20000` are capped to `20000` (`MAX_DRAIN_JITTER_MS`) to leave close-frame delivery headroom under the relay's 30s hard-drain timeout. Empty or whitespace-only is treated as unset (off); a non-integer fails startup loudly. |
|
||
| `BUZZ_AUDIT_ENABLED` | `true` | Tamper-evident event/media audit log. Set `false`/`0`/`off` to skip its DB pool and writes. Does not disable the separate moderation audit trail. |
|
||
| `BUZZ_AUTO_MIGRATE` | `false` | Opt in with `true`/`1`/`yes`/`on` to run embedded SQLx migrations on relay startup |
|
||
| `RELAY_OWNER_PUBKEY` | unset | Bootstrapped as `owner` in `relay_members` at first start |
|
||
| `BUZZ_ALLOW_NIP_OA_AUTH` | `false` | Enable NIP-OA owner attestation for membership |
|
||
| `BUZZ_WEB_DIR` | unset (source), `/srv/buzz/web` (container) | Directory containing the invite landing bundle; the production container enables it so `/invite/{code}` always works |
|
||
| `BUZZ_SERVE_GIT_WEB_GUI` | `false` | Set to `true` or `1` to expose the bundled Git repository browser at `/` and `/repos/...`; invite routes do not depend on this flag |
|
||
|
||
CLI-side, only two matter for testing:
|
||
|
||
| Variable | Default | Notes |
|
||
|-------------------------|--------------------------|-------|
|
||
| `BUZZ_RELAY_URL` | `http://localhost:3000` | CLI relay base; accepts `ws(s)://` and normalises |
|
||
| `BUZZ_PRIVATE_KEY` | — (**required**) | `nsec1…` or 64-char hex |
|
||
| `BUZZ_AUTH_TAG` | unset | Optional NIP-OA owner attestation JSON |
|
||
|
||
---
|
||
|
||
## Troubleshooting
|
||
|
||
| Symptom | Cause | Fix |
|
||
|---------|-------|-----|
|
||
| `relay error 500` or `400: restricted: not a channel member` after a code change | Stale binary | Rebuild and re-export `PATH`; or `cargo run` directly |
|
||
| `Address already in use` on relay start (os error 48 on macOS, 98 on Linux) | Another relay (or stale process) holding `:3000` / `:8080` / `:9102` (or your override ports) | The panic line names the failing port — read it first. Then `lsof -iTCP:3000,8080,9102 -sTCP:LISTEN` (or your override equivalents). Kill the offender (`pkill -f buzz-relay`) or use the port-override block in step 3. If you already overrode and *still* collide, a prior reviewer left a relay running on the same alt ports — kill it or pick fresh ports |
|
||
| `auth_error: BUZZ_PRIVATE_KEY is required` | Env not exported into the CLI's shell | `export BUZZ_PRIVATE_KEY=...` (or pass `--private-key`) |
|
||
| `auth_error: BUZZ_AUTH_TAG verification failed … signature verification failed` | A stale `BUZZ_AUTH_TAG` inherited from a parent shell. The local dev relay rejects it. | `unset BUZZ_AUTH_TAG` (see the scrub block in step 1) |
|
||
| `auth-required: verification failed` on a closed relay | NIP-OA attestation needed | Set `BUZZ_AUTH_TAG` to the owner-issued JSON, or relax `BUZZ_REQUIRE_RELAY_MEMBERSHIP` |
|
||
| `channels list` empty after `channels create` | The CLI doesn't echo the channel UUID; use the filter shown in step 4 | Or `POST /query` with `{"kinds":[39002]}` |
|
||
| ACP agent ignores all events | `BUZZ_ACP_RESPOND_TO=owner-only` (default) with no owner configured | Set `BUZZ_ACP_RESPOND_TO=anyone` for testing |
|
||
| ACP logs `discovered 0 channel(s)` / `no channel subscriptions resolved` | Agent identity isn't a member of any channel | `buzz channels add-member --channel "$CHANNEL" --pubkey "$AGENT_PUBKEY" --role member` from another identity |
|
||
| `GOOSE_MODE` warning, agent hangs | Not set | `export GOOSE_MODE=auto` |
|
||
| Tests pass locally but CI fails | Forgot to run `just ci` | `just ci` runs the gate (fmt, clippy, unit tests, desktop/web builds) |
|