fix(recording): finish a recording the bot died in the middle of (#399) #470
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/399-recover-interrupted-recordings"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes the last open criterion on #399.
The gap
A crash mid-session leaves per-speaker raw capture tracks on disk and nothing else — no session directory, no manifest, no handoff marker, because all three are written at stop. #399's first pass stopped the startup sweep from deleting them and logged
needs finalising by hand. Nobody has hands on a self-hosted bot at 2am, so an hour into a four-hour session sat on the volume indefinitely.What lands
The bot converts what it captured on its next start and hands it over. The session then appears like any other.
Automating this is only defensible because placement in the raw tracks is absolute — each track is silence-padded from the session's t0 before every append — so the tracks are already mutually aligned and the duration is exactly
max(file size) / BYTES_PER_SECOND. Nothing is reconstructed or estimated; the timings and attribution are the recorded ones. Per-speaker private clocks would make recovery impossible, which is the whole of #320. That dependency is now written intoCLAUDE.mdso it isn't quietly broken later.Wiring. A background task off
on_ready, notsetup_hook, for two reasons: resolving a speaker's guild nickname needs a warm member cache that doesn't exist insetup_hook, and re-encoding hours of audio must not sit in front of the bot answering commands. A player who renamed themselves to their character loses that identity to the global name (#344) — the sessions that already went wrong shouldn't also read worst. Guarded to run once per process, sinceon_readyre-fires on every gateway reconnect.API change
GET /api/bot/sessions/{id}/summarynow returnsguild_idandnotification_channel_id. Neither is derivable from a filename, and both are load-bearing:POST /audiomatches the session against the guild id.process_audiopasses the channel straight tosession_summarised, and the bot drops an event with an empty channel_id. Without it a recovered session would transcribe, summarise, and then say nothing in Discord — indistinguishable from the recovery not having run.Additive optional fields on a response, so no
BOT_CONTRACT_VERSIONbump (confirmed withcheck_version_sync.py).Every path errs towards leaving the audio alone
_retry_pending_handoffsruns first and may have just succeeded; the 409 guard would not catch the double submit, because the first run hasn't written a transcript yetA raw track is unlinked only after the handoff is acked, so nothing is ever the sole copy of something converted from a file already gone.
Verification
15 new bot tests + 2 backend tests, all run against unfixed code first and confirmed to fail. Mutations checked:
guild_id/notification_channel_iddropped from the response — caughtguild_idemitted as""rather thannull— caughtFull bot suite: 244 passed. Backend
test_bot.py: 50 passed.ruff check+ruff format --checkclean onwebapp/backend/ scripts/.🤖 Generated with Claude Code