fix(bot): never sweep a directory that holds a real recording (#399) #447

Merged
claude-bot merged 1 commit from fix/399-never-sweep-real-audio into main 2026-08-29 22:39:38 +00:00
Contributor

Closes the data-loss half of #399 (CRITICAL).

The defect

The startup sweep deleted any session directory that was unmarked and older than an hour — and the marker meaning "the backend owns this" was only written after a successful upload ack. Four ordinary situations therefore left hours of captured speech looking exactly like abandoned scratch:

  • the upload POST failing (backend restart, deploy, a few seconds of network trouble)
  • a crash between the ack and the marker write
  • a crash mid-recording, leaving raw .s16le capture on disk
  • #397's own branch, where the backend declines to overwrite an existing transcript

That last one isn't in the issue. It was introduced by a fix, in this same milestone, and it's the sharpest version of the bug — it tells the GM in bold that "Nothing was lost — this recording is saved separately" and then returns without marking anything. The reassurance and the deletion are eleven lines apart.

The sweep's docstring asserted "the backend never received the upload so there is nothing to preserve." That sentence is exactly backwards: an upload the backend never received is precisely when the bot holds the only copy.

The fix

The test is no longer "is it marked and old" but "can I prove there is nothing here to lose." A directory or raw file containing a non-zero-byte track is kept regardless of age or marker, and reported in the log. Only provably-empty scratch is removed.

Disk growth is recoverable; a deleted session is not — the same standard #427 settled on for the backend, now applied to the bot.

Beyond stopping the loss, a failed handoff is retried on the next startup, before anything sweeps. A manifest written when the session directory is created carries the exact payload, so the retry replays the original request rather than reconstructing one. That matters concretely: duration_seconds would otherwise default to 0, which silently disables the backend's coverage guards — the mechanism behind #421. The backend refusing to overwrite is recorded as terminal, so that take is kept but not re-asked on every start.

Two existing tests asserted the destructive behaviour

test_cleanup_removes_old_orphaned_session_dir wrote a non-empty 123.wav and required the sweep to delete it. test_cleanup_removes_old_raw_wav did the same for raw capture. They were the defect written down as the specification — exactly the pattern #441 exists to hunt — and are inverted here, with the history noted in their docstrings so nobody "fixes" them back.

Verification

Mutation-checked: with the content guard bypassed, three tests fail, including the issue's own scenario (four-hour session, upload fails during maintenance, bot redeployed two hours later).

218 bot tests pass (was 211), 1,409 backend.

Not done here

Finalising a mid-recording crash's raw tracks into WAVs and handing them off. They're now preserved and logged with their path rather than deleted, which stops the data loss — but converting them needs session metadata the bot no longer holds at that point. #399 stays open for it rather than being closed on a partial.

Note on branches

This and PR #446 were briefly stacked by mistake — #446 also carried the gitleaks commit. Both have been rebased onto main and force-pushed, so each PR is now a single independent commit. Worth a fresh look at #446's diff if you'd already reviewed it.

🤖 Generated with Claude Code

Closes the data-loss half of #399 (CRITICAL). ## The defect The startup sweep deleted any session directory that was unmarked and older than an hour — and the marker meaning *"the backend owns this"* was only written **after** a successful upload ack. Four ordinary situations therefore left hours of captured speech looking exactly like abandoned scratch: - the upload POST failing (backend restart, deploy, a few seconds of network trouble) - a crash between the ack and the marker write - a crash mid-recording, leaving raw `.s16le` capture on disk - **#397's own branch**, where the backend declines to overwrite an existing transcript That last one isn't in the issue. It was introduced **by a fix, in this same milestone**, and it's the sharpest version of the bug — it tells the GM in bold that *"**Nothing was lost** — this recording is saved separately"* and then returns without marking anything. The reassurance and the deletion are eleven lines apart. The sweep's docstring asserted *"the backend never received the upload so there is nothing to preserve."* That sentence is exactly backwards: an upload the backend never received is precisely when the bot holds the **only** copy. ## The fix The test is no longer "is it marked and old" but **"can I prove there is nothing here to lose."** A directory or raw file containing a non-zero-byte track is kept regardless of age or marker, and reported in the log. Only provably-empty scratch is removed. Disk growth is recoverable; a deleted session is not — the same standard #427 settled on for the backend, now applied to the bot. **Beyond stopping the loss**, a failed handoff is retried on the next startup, before anything sweeps. A manifest written when the session directory is created carries the exact payload, so the retry replays the original request rather than reconstructing one. That matters concretely: `duration_seconds` would otherwise default to `0`, which silently disables the backend's coverage guards — the mechanism behind #421. The backend refusing to overwrite is recorded as terminal, so that take is kept but not re-asked on every start. ## Two existing tests asserted the destructive behaviour `test_cleanup_removes_old_orphaned_session_dir` wrote a **non-empty** `123.wav` and required the sweep to delete it. `test_cleanup_removes_old_raw_wav` did the same for raw capture. They were the defect written down as the specification — exactly the pattern #441 exists to hunt — and are inverted here, with the history noted in their docstrings so nobody "fixes" them back. ## Verification Mutation-checked: with the content guard bypassed, three tests fail, including the issue's own scenario (four-hour session, upload fails during maintenance, bot redeployed two hours later). **218 bot tests pass** (was 211), 1,409 backend. ## Not done here Finalising a mid-recording crash's raw tracks into WAVs and handing them off. They're now **preserved and logged with their path** rather than deleted, which stops the data loss — but converting them needs session metadata the bot no longer holds at that point. #399 stays open for it rather than being closed on a partial. ## Note on branches This and PR #446 were briefly stacked by mistake — #446 also carried the gitleaks commit. Both have been rebased onto `main` and force-pushed, so each PR is now a single independent commit. Worth a fresh look at #446's diff if you'd already reviewed it. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(bot): never sweep a directory that holds a real recording (#399)
All checks were successful
CI / Bot/backend version sync (pull_request) Successful in 23s
CI / Backend lint (ruff) (pull_request) Successful in 45s
CI / Frontend tests, audit, and build (pull_request) Successful in 1m30s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m29s
CI / Bot tests and audit (pull_request) Successful in 2m5s
CI / Docker image build (pull_request) Successful in 4m37s
CI / Backend migration, tests, and audit (pull_request) Successful in 7m39s
1f799f0942
The startup sweep deleted any session directory that was unmarked and older
than an hour, and the marker meaning "the backend owns this" was only written
*after* a successful upload ack. Four ordinary situations therefore left hours
of captured speech looking exactly like abandoned scratch:

  - the upload POST failing (backend restart, deploy, brief network trouble)
  - a crash between the ack and the marker write
  - a crash mid-recording, leaving raw .s16le capture on disk
  - #397's own branch, where the backend declines to overwrite an existing
    transcript — which tells the GM in bold that **nothing was lost** and then
    returns without marking anything

The last one is not in the issue. It was introduced by a fix, in this same
milestone, and is the sharpest version of the bug: the reassurance and the
deletion are eleven lines apart.

The sweep's docstring asserted "the backend never received the upload so there
is nothing to preserve". That sentence is exactly backwards — an upload the
backend never received is precisely when the bot holds the only copy.

So the test is no longer "is it marked and old" but "can I prove there is
nothing here to lose". A directory or raw file containing a non-zero-byte
track is kept regardless of age or marker, and reported. Only provably-empty
scratch is removed. Disk growth is recoverable; a deleted session is not —
the standard #427 settled on for the backend, applied to the bot.

Beyond stopping the loss, a failed handoff is now retried on the next startup,
before anything sweeps. A manifest written when the session directory is
created carries the exact payload, so the retry replays the original request
rather than reconstructing one — notably duration_seconds, which defaulted to
0 and would have silently disabled the backend's coverage guards (#421). The
backend refusing to overwrite is recorded as terminal, so it is kept but not
re-asked every start.

Two existing tests asserted the destructive behaviour — a non-empty WAV,
required to be deleted — and are inverted here. They were the defect written
down as the specification, which is the pattern #441 exists to hunt.

Verified by mutation: with the content check bypassed, the three tests that
matter fail, including the issue's own scenario (failed upload, restart past
the age threshold). 218 bot tests pass, 1,409 backend.

Not done here: finalising a mid-recording crash's raw tracks into WAVs and
handing them off. They are preserved and logged rather than converted; #399
stays open for that.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
rbrooks force-pushed fix/399-never-sweep-real-audio from 1f799f0942
All checks were successful
CI / Bot/backend version sync (pull_request) Successful in 23s
CI / Backend lint (ruff) (pull_request) Successful in 45s
CI / Frontend tests, audit, and build (pull_request) Successful in 1m30s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m29s
CI / Bot tests and audit (pull_request) Successful in 2m5s
CI / Docker image build (pull_request) Successful in 4m37s
CI / Backend migration, tests, and audit (pull_request) Successful in 7m39s
to 825ec24287
All checks were successful
CI / Backend lint (ruff) (pull_request) Successful in 28s
CI / Bot/backend version sync (pull_request) Successful in 50s
CI / Bot tests and audit (pull_request) Successful in 1m16s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m34s
CI / Frontend tests, audit, and build (pull_request) Successful in 2m12s
CI / Docker image build (pull_request) Successful in 2m34s
CI / Backend migration, tests, and audit (pull_request) Successful in 8m6s
2026-08-29 22:28:03 +00:00
Compare
claude-bot deleted branch fix/399-never-sweep-real-audio 2026-08-29 22:39:39 +00:00
Sign in to join this conversation.
No description provided.