Skip to content

R2 base64 compatibility removal#684

Merged
kentcdodds merged 6 commits intomainfrom
cursor/r2-base64-compatibility-removal-b474
Feb 24, 2026
Merged

R2 base64 compatibility removal#684
kentcdodds merged 6 commits intomainfrom
cursor/r2-base64-compatibility-removal-b474

Conversation

@kentcdodds
Copy link
Owner

@kentcdodds kentcdodds commented Feb 24, 2026

Remove pre-R2 Call.base64 compatibility after R2 backfill.


Open in Web Open in Cursor 


Note

Medium Risk
Includes a DB schema migration that permanently drops Call.base64 and removes runtime fallback behavior, so any incomplete backfill or overlooked dependencies would break access to legacy call audio.

Overview
Call Kent caller audio is now strictly key/metadata-based: Call.base64 is removed from the Prisma schema and a migration drops the column with a preflight guard that aborts if any rows still have base64 but no audioKey.

Audio serving (call-audio loader) no longer falls back to DB base64; it 404s when audioKey is missing and, when audioSize is absent/invalid, resolves size (and optionally content-type) via new headAudioObject() before falling back to buffering.

Storage utilities add HEAD support via HeadObjectCommand, expand mocks to handle HEAD, add a test for HEAD lookups, and remove legacy base64 migration logic from episode draft processing; seeding also drops the base64 call fixture.

Written by Cursor Bugbot for commit 1782afb. This will update automatically on new commits. Configure here.

Summary by CodeRabbit

  • Refactor

    • Standardized caller audio on key-based storage and removed legacy embedded base64 handling; simplified and unified audio streaming with proper range support.
    • Updated database schema to drop the legacy base64 field.
  • Bug Fixes

    • Requests for calls missing an audio key now return 404 to avoid ambiguous behavior.
  • Tests

    • Added in-memory test cache utilities for isolated, deterministic caching in tests.

@cursor
Copy link

cursor bot commented Feb 24, 2026

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

@coderabbitai
Copy link

coderabbitai bot commented Feb 24, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR removes legacy base64 audio storage and fallback handling from the Call workflow, requires calls to reference audio via audioKey, updates streaming to use getAudioBuffer/getAudioStream, removes the base64 field from the Prisma schema and seed, adds a migration to drop the column, and introduces a test in-memory cache utility with resetTestCache().

Changes

Cohort / File(s) Summary
Audio streaming & call handling
app/routes/resources/calls/call-audio.ts, app/routes/resources/calls/save.tsx, app/utils/call-kent-episode-draft.server.ts
Removed legacy base64 parsing and storage-migration paths; enforce audioKey presence; consolidated streaming logic to use getAudioBuffer/getAudioStream with proper range handling and consistent response headers.
Database schema & migration
prisma/schema.prisma, prisma/migrations/20260224171027_drop-call-base64/migration.sql
Dropped base64 field from Call; added migration with a preflight guard to ensure backfill completeness, recreating the Call table and restoring indexes/constraints.
Seed data
prisma/seed.ts
Removed seed entry that created a Call including the legacy base64 value.
Test cache utilities & tests
tests/semantic-search-test-cache.ts, app/utils/__tests__/semantic-search-cache.server.test.ts, app/utils/__tests__/semantic-search.server.test.ts
Added in-memory test cache and resetTestCache() helper; updated tests to use resetTestCache() instead of direct memory.clear() access; provided a cachified wrapper spy for deterministic tests.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

  • Issue #665: Matches the effort to remove pre-R2 Call.base64 fallback and related code paths and schema changes.

Possibly related PRs

  • PR #663: Alters call-kent-episode-draft.server.ts and audio handling similarly—likely coordinated changes.
  • PR #656: Modifies app/routes/resources/calls/save.tsx alongside this PR’s removal of legacy base64 insertion.

Poem

🐰
I hopped through lines of legacy paste,
buried base64 without a waste,
audioKey now leads the tune,
streaming neat from sun to moon,
code trimmed light — we celebrate soon!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'R2 base64 compatibility removal' directly and clearly describes the main objective of the changeset: removing legacy base64 audio compatibility after migration to R2 storage.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch cursor/r2-base64-compatibility-removal-b474

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@kentcdodds kentcdodds marked this pull request as ready for review February 24, 2026 17:30
@cursor cursor bot force-pushed the cursor/r2-base64-compatibility-removal-b474 branch from cd7a125 to 3b77026 Compare February 24, 2026 18:25
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
prisma/migrations/20260224171027_drop-call-base64/migration.sql (1)

17-18: Confirm R2 backfill is complete before running this migration.

The INSERT copies audioKey (which may be NULL) but discards base64. Any Call rows where audioKey IS NULL and a base64 value existed will have their audio irrecoverably dropped — there is no rollback path once the old table is gone. The PR asserts the backfill is done, but the migration itself has no guard.

Before deploying, consider running a preflight query against production:

SELECT COUNT(*) FROM "Call" WHERE "audioKey" IS NULL;

If the count is non-zero, those records will serve 404s after migration. Acceptable if intentional; worth a deliberate acknowledgment before the column is dropped.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@prisma/migrations/20260224171027_drop-call-base64/migration.sql` around lines
17 - 18, The migration drops the old "Call" table but the INSERT into "new_Call"
copies "audioKey" and discards "base64", which will irreversibly lose audio for
rows where "audioKey" IS NULL; update the migration.sql to guard or preserve
that data: either add a preflight check that aborts the migration (e.g., SELECT
COUNT(*) FROM "Call" WHERE "audioKey" IS NULL and RAISE/FAIL if >0) so engineers
must confirm backfill is complete, or modify the INSERT/CREATE so the "base64"
column is preserved/copied into an appropriate column on "new_Call" (or into a
temporary column) before dropping "Call" (referencing "Call", "new_Call",
"audioKey", and "base64"); ensure the migration fails fast if the preflight
check detects any rows with audioKey IS NULL.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@prisma/migrations/20260224171027_drop-call-base64/migration.sql`:
- Around line 17-18: The migration drops the old "Call" table but the INSERT
into "new_Call" copies "audioKey" and discards "base64", which will irreversibly
lose audio for rows where "audioKey" IS NULL; update the migration.sql to guard
or preserve that data: either add a preflight check that aborts the migration
(e.g., SELECT COUNT(*) FROM "Call" WHERE "audioKey" IS NULL and RAISE/FAIL if
>0) so engineers must confirm backfill is complete, or modify the INSERT/CREATE
so the "base64" column is preserved/copied into an appropriate column on
"new_Call" (or into a temporary column) before dropping "Call" (referencing
"Call", "new_Call", "audioKey", and "base64"); ensure the migration fails fast
if the preflight check detects any rows with audioKey IS NULL.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cd7a125 and 3b77026.

📒 Files selected for processing (7)
  • app/routes/resources/calls/call-audio.ts
  • app/routes/resources/calls/save.tsx
  • app/utils/call-kent-episode-draft.server.ts
  • app/utils/simplecast.server.ts
  • prisma/migrations/20260224171027_drop-call-base64/migration.sql
  • prisma/schema.prisma
  • prisma/seed.ts
💤 Files with no reviewable changes (1)
  • app/routes/resources/calls/save.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • app/routes/resources/calls/call-audio.ts
  • prisma/seed.ts
  • app/utils/call-kent-episode-draft.server.ts

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/semantic-search-test-cache.ts`:
- Around line 3-16: The module-global Map "memory" can leak state between tests;
add and export a reset helper (e.g., resetTestCache or clearMemory) that calls
memory.clear(), update the exported API around testCache to include that
function, and use it from test setup/teardown to ensure memory is cleared
before/after each test run; reference the existing "memory" and "testCache"
symbols when implementing the helper.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3b77026 and ef2d2e9.

📒 Files selected for processing (1)
  • tests/semantic-search-test-cache.ts

Comment on lines +3 to +16
export const memory = new Map<string, unknown>()

export const testCache = {
name: 'test-cache',
get(key: string) {
return (memory.get(key) as any) ?? null
},
async set(key: string, entry: unknown) {
memory.set(key, entry)
},
async delete(key: string) {
memory.delete(key)
},
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Prevent cross-test cache state leakage.

memory is module‑global; without a reset, cached entries can bleed across tests and make outcomes order‑dependent. Consider exporting a reset helper (and using it in test setup).

♻️ Suggested helper
 export const memory = new Map<string, unknown>()
+
+export function resetTestCache() {
+	memory.clear()
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const memory = new Map<string, unknown>()
export const testCache = {
name: 'test-cache',
get(key: string) {
return (memory.get(key) as any) ?? null
},
async set(key: string, entry: unknown) {
memory.set(key, entry)
},
async delete(key: string) {
memory.delete(key)
},
}
export const memory = new Map<string, unknown>()
export function resetTestCache() {
memory.clear()
}
export const testCache = {
name: 'test-cache',
get(key: string) {
return (memory.get(key) as any) ?? null
},
async set(key: string, entry: unknown) {
memory.set(key, entry)
},
async delete(key: string) {
memory.delete(key)
},
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/semantic-search-test-cache.ts` around lines 3 - 16, The module-global
Map "memory" can leak state between tests; add and export a reset helper (e.g.,
resetTestCache or clearMemory) that calls memory.clear(), update the exported
API around testCache to include that function, and use it from test
setup/teardown to ensure memory is cleared before/after each test run; reference
the existing "memory" and "testCache" symbols when implementing the helper.

@cursor cursor bot force-pushed the cursor/r2-base64-compatibility-removal-b474 branch from ef2d2e9 to 79f9bfd Compare February 24, 2026 19:21
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
app/routes/resources/calls/call-audio.ts (1)

36-57: Buffer fallback for missing audioSize is reasonable.

This path loads the full audio into memory, which is appropriate as a rare safety net. One note: the comment on line 37 correctly flags that this "should" not happen for well-formed data — consider logging a warning here so you can detect if rows are missing audioSize in production and fix them proactively.

🔧 Optional: add observability for the fallback path
 	if (typeof size !== 'number' || !Number.isFinite(size) || size <= 0) {
 		// Size should always be present for stored audio; keep a safe fallback.
+		console.warn(`call-audio: audioSize missing/invalid for key=${audioKey}, buffering full object`)
 		const buffer = await getAudioBuffer({ key: audioKey })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/routes/resources/calls/call-audio.ts` around lines 36 - 57, In the
fallback block that handles missing/invalid size (the code around
getAudioBuffer({ key: audioKey }) and parseHttpByteRangeHeader(rangeHeader,
size)), add a warning log immediately after obtaining buffer.byteLength that
records the audioKey and resolved size to surface missing audioSize rows in
production; use the module's existing logger (e.g., processLogger.warn) if
available or fall back to console.warn, and keep the message concise like
"Missing audioSize for audioKey=<...>, using resolved size=<...>" so operators
can triage and fix the data.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/routes/resources/calls/call-audio.ts`:
- Around line 36-57: In the fallback block that handles missing/invalid size
(the code around getAudioBuffer({ key: audioKey }) and
parseHttpByteRangeHeader(rangeHeader, size)), add a warning log immediately
after obtaining buffer.byteLength that records the audioKey and resolved size to
surface missing audioSize rows in production; use the module's existing logger
(e.g., processLogger.warn) if available or fall back to console.warn, and keep
the message concise like "Missing audioSize for audioKey=<...>, using resolved
size=<...>" so operators can triage and fix the data.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ef2d2e9 and 79f9bfd.

📒 Files selected for processing (9)
  • app/routes/resources/calls/call-audio.ts
  • app/routes/resources/calls/save.tsx
  • app/utils/__tests__/semantic-search-cache.server.test.ts
  • app/utils/__tests__/semantic-search.server.test.ts
  • app/utils/call-kent-episode-draft.server.ts
  • prisma/migrations/20260224171027_drop-call-base64/migration.sql
  • prisma/schema.prisma
  • prisma/seed.ts
  • tests/semantic-search-test-cache.ts
💤 Files with no reviewable changes (2)
  • app/routes/resources/calls/save.tsx
  • prisma/seed.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/semantic-search-test-cache.ts

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
app/routes/resources/calls/call-audio.ts (1)

35-59: Consider avoiding full-buffer fallback if this can be frequent.
Loading the entire audio into memory to infer size can spike memory on large files; consider a storage metadata/HEAD size lookup or a capped/short-circuit path if audioSize is missing often.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/routes/resources/calls/call-audio.ts` around lines 35 - 59, The code
currently falls back to loading the entire blob via getAudioBuffer to compute
size when size is invalid, which can spike memory; change the fallback in the
block that handles typeof size !== 'number' (around variables size, audioKey,
callId, rangeHeader) to first attempt a metadata/HEAD size lookup from the
storage layer (e.g., a new getAudioMetadata or headObject call) to set size
without downloading the body, only if that fails then use a capped fallback:
either stream the file without buffering (use the storage streaming API to pipe
into Readable without buffering full content) or return a short-circuit response
indicating missing size (e.g., 416 or 400) for very large files. Ensure
parseHttpByteRangeHeader(rangeHeader, size), Readable.from /
createReadableStreamFromReadable usage and the response header construction
(Content-Range/Content-Length) still operate on the obtained size/stream
semantics.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/routes/resources/calls/call-audio.ts`:
- Around line 35-59: The code currently falls back to loading the entire blob
via getAudioBuffer to compute size when size is invalid, which can spike memory;
change the fallback in the block that handles typeof size !== 'number' (around
variables size, audioKey, callId, rangeHeader) to first attempt a metadata/HEAD
size lookup from the storage layer (e.g., a new getAudioMetadata or headObject
call) to set size without downloading the body, only if that fails then use a
capped fallback: either stream the file without buffering (use the storage
streaming API to pipe into Readable without buffering full content) or return a
short-circuit response indicating missing size (e.g., 416 or 400) for very large
files. Ensure parseHttpByteRangeHeader(rangeHeader, size), Readable.from /
createReadableStreamFromReadable usage and the response header construction
(Content-Range/Content-Length) still operate on the obtained size/stream
semantics.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 79f9bfd and 94d9141.

📒 Files selected for processing (1)
  • app/routes/resources/calls/call-audio.ts

cursoragent and others added 6 commits February 24, 2026 19:40
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
@cursor cursor bot force-pushed the cursor/r2-base64-compatibility-removal-b474 branch from 409e521 to 1782afb Compare February 24, 2026 19:43
@kentcdodds kentcdodds merged commit 92f956d into main Feb 24, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants