fix(bot,backend): never record without announcing it (#839) #842

Merged
claude-bot merged 3 commits from fix/recording-announcement into main 2026-09-26 16:57:44 +00:00
Collaborator

Closes #839. This is the consent model's one requirement: a channel announcement when recording begins.

Before: a web-started recording in a campaign with no Discord channel recorded with no announcement. If the announcement post failed, capture kept running while the page reported a failure.

Now: both start paths go join voice → announce → listen.

  • Where it announces: the campaign's channel first. If there's none, or the post fails, the voice channel's own text chat, where the people being recorded are. This is the owner's O3(a), provisional until they answer.
  • If neither takes the post, the bot disconnects without ever listening: no sink, no file.
    • The web path returns 409 with a plain reason: "I couldn't announce the recording, so I haven't started it…"
    • /record start refuses, and tells the GM why.
  • The backend never sends channel_id "0", and omits it when there's no channel. A new bot treats missing, null, "" or "0" as "no channel". An older bot's 400 on a missing channel is reworded plainly for the GM.
  • A latent bug fixed along the way: a web-started recording with no channel crashed _stop_recording on None.send, so its audio only reached the backend through crash recovery.
  • D96 wording: the upload embed and /record stop's reply no longer promise the summary in the channel; it's posted once a GM approves it.

Contract: BOT_CONTRACT_VERSION stays 2. /record/start is the bot's internal HTTP route, and the change is additive: an optional request field and a new announced_in in the reply. check_version_sync.py passes.

Tests (all written first, and failing on main):

  • Bot: the full suite, 479 passed. It covers every path, the announce-before-listen order, and the "both fail, nothing captured" case.
  • Backend: 188 targeted tests.
  • The e2e fake bot is updated.
  • ruff is clean; bot/ was left unformatted, as always.

Follow-ups (a new issue):

  • /record start's refusal may be public, because the command defers publicly.
  • On the Discord path, the session is marked live (D116) before the bot connects, so a refusal leaves it in progress.

🤖 Generated with Claude Code

Closes #839. **This is the consent model's one requirement:** a channel announcement when recording begins. **Before:** a web-started recording in a campaign with no Discord channel recorded with no announcement. If the announcement post failed, capture kept running while the page reported a failure. **Now:** both start paths go **join voice → announce → listen**. - **Where it announces:** the campaign's channel first. If there's none, or the post fails, the voice channel's own text chat, where the people being recorded are. This is the owner's O3(a), provisional until they answer. - **If neither takes the post,** the bot disconnects **without ever listening**: no sink, no file. - **The web path** returns 409 with a plain reason: "I couldn't announce the recording, so I haven't started it…" - **`/record start`** refuses, and tells the GM why. - **The backend never sends `channel_id "0"`,** and omits it when there's no channel. A new bot treats missing, null, `""` or `"0"` as "no channel". An older bot's 400 on a missing channel is reworded plainly for the GM. - **A latent bug fixed along the way:** a web-started recording with no channel crashed `_stop_recording` on `None.send`, so its audio only reached the backend through crash recovery. - **D96 wording:** the upload embed and `/record stop`'s reply no longer promise the summary in the channel; it's posted once a GM approves it. **Contract:** `BOT_CONTRACT_VERSION` stays 2. `/record/start` is the bot's internal HTTP route, and the change is additive: an optional request field and a new `announced_in` in the reply. `check_version_sync.py` passes. **Tests (all written first, and failing on main):** - **Bot:** the full suite, 479 passed. It covers every path, the announce-before-listen order, and the "both fail, nothing captured" case. - **Backend:** 188 targeted tests. - **The e2e fake bot** is updated. - ruff is clean; `bot/` was left unformatted, as always. **Follow-ups** (a new issue): - `/record start`'s refusal may be public, because the command defers publicly. - On the Discord path, the session is marked live (D116) before the bot connects, so a refusal leaves it in progress. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(bot,webapp): announce a recording before capturing it, or don't record (#839)
Some checks failed
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 2m7s
CI / Backend lint (ruff) (pull_request) Successful in 55s
CI / Feedback site tests (pull_request) Successful in 47s
CI / Does this change touch the recording path? (pull_request) Has been cancelled
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been cancelled
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Has been cancelled
CI / Bot tests and audit (pull_request) Has been cancelled
CI / Backend migration, tests, and audit (pull_request) Has been cancelled
CI / Frontend tests, audit, and build (pull_request) Has been cancelled
CI / Docker image build (pull_request) Has been cancelled
CI / Bot/backend version sync (pull_request) Has been cancelled
44e6d95679
The group's standing consent has one condition: the channel is told when
recording begins. A recording started from the web page could run with no
announcement: a campaign with no channel sent "0", which the bot could not
find and so recorded silently; and a post that failed came after capture had
started, leaving the bot recording while the page said it had failed.

The bot now joins voice, announces, and only then listens. The web path tries
the campaign's channel, then the voice channel's own chat; /record start tries
the channel it was run in, then the voice chat. If neither takes the post it
leaves voice, captures nothing, and says why: a 409 the recording panel shows,
or a private reply to the GM. The recording's text channel (stop messages,
auto-stop, the restart explanation) is wherever the start was announced.

The backend omits channel_id when the campaign has none, instead of "0". The
bot reads a missing, empty or "0" channel as none, so an older backend still
works; an older bot refuses a start without a channel, which the backend words
plainly. /record/start's reply gains announced_in. Additive: no contract bump.

Also D96's wording: the upload post and /record stop no longer promise the
summary in the channel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge remote-tracking branch 'origin/main' into fix/recording-announcement
All checks were successful
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m22s
CI / Backend lint (ruff) (pull_request) Successful in 45s
CI / Bot/backend version sync (pull_request) Successful in 31s
CI / Does this change touch the recording path? (pull_request) Successful in 35s
CI / Bot tests and audit (pull_request) Successful in 3m28s
CI / Feedback site tests (pull_request) Successful in 1m27s
CI / Docker image build (pull_request) Successful in 2m27s
CI / Backend migration, tests, and audit (pull_request) Successful in 13m40s
CI / Frontend tests, audit, and build (pull_request) Successful in 17m58s
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Successful in 21m8s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Successful in 32m49s
e9a9ee4a89
# Conflicts:
#	CHANGELOG.md
Merge remote-tracking branch 'origin/main' into fix/recording-announcement
All checks were successful
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m0s
CI / Bot tests and audit (pull_request) Successful in 2m15s
CI / Feedback site tests (pull_request) Successful in 46s
CI / Backend lint (ruff) (pull_request) Successful in 1m19s
CI / Bot/backend version sync (pull_request) Successful in 27s
CI / Does this change touch the recording path? (pull_request) Successful in 23s
CI / Docker image build (pull_request) Successful in 1m15s
CI / Frontend tests, audit, and build (pull_request) Successful in 18m52s
CI / Backend migration, tests, and audit (pull_request) Successful in 34m40s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Successful in 12m20s
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Successful in 29m57s
a3201855e3
# Conflicts:
#	CHANGELOG.md
Sign in to join this conversation.
No description provided.