Skip to content

[codex] Move R2 deletes to delayed prefix#2297

Open
riderx wants to merge 1 commit into
mainfrom
codex/r2-delayed-delete-safety
Open

[codex] Move R2 deletes to delayed prefix#2297
riderx wants to merge 1 commit into
mainfrom
codex/r2-delayed-delete-safety

Conversation

@riderx
Copy link
Copy Markdown
Member

@riderx riderx commented May 19, 2026

Summary (AI generated)

  • Move the R2 bundle object for a soft-deleted app_versions row into the trash prefix instead of deleting it directly.
  • Keep the original object path after the prefix: deleted-after-7-days/<original r2_path>.
  • Add the tracked Cloudflare R2 lifecycle config for the capgo bucket trash prefix.
  • Update the targeted bundle cleanup unit test.

Motivation (AI generated)

Accidental direct deletes in R2 are hard to recover. Moving the bundle to a lifecycle-managed trash prefix gives a 7-day recovery window while keeping the live bundle path cleared.

Business Impact (AI generated)

This reduces irreversible bundle-loss risk when an app version is deleted by mistake, without changing upload retry cleanup, scripts, app deletion cleanup, or manifest cleanup behavior.

Test Plan (AI generated)

  • bunx vitest run tests/on-version-update-cleanup.unit.test.ts
  • bun lint:backend
  • bun typecheck
  • git diff --check
  • bun -e "const fs = require('node:fs'); const config = JSON.parse(fs.readFileSync('cloudflare_workers/r2/lifecycle.capgo.json', 'utf8')); if (config.rules?.[0]?.deleteObjectsTransition?.condition?.maxAge !== 604800) process.exit(1);"

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 19, 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

Converts immediate S3/R2 deletion to a staged trash-based move: adds a trash helper, integrates it into the version-update cleanup trigger, updates unit tests to mock/verify move-to-trash behavior, and adjusts demo cleanup SQL expectations.

Changes

Delayed-delete for version cleanup

Layer / File(s) Summary
S3 trash prefix and moveObjectToTrash helper
supabase/functions/_backend/utils/s3.ts
Introduces deleted-after-7-days/ prefix and key helper, adds isMissingObjectError, and implements moveObjectToTrash(c, fileId) to copy objects into the trash prefix then delete originals; missing-source errors are treated as success and failures are logged.
Version cleanup trigger integration
supabase/functions/_backend/triggers/on_version_update.ts
Replaces s3.deleteObject with s3.moveObjectToTrash in deleteIt; records a moved boolean and throws simpleError('cannot_move_s3_to_trash', ...) if the move fails by exception or falsy return; updates log/error text.
Unit test mock and validation updates
tests/on-version-update-cleanup.unit.test.ts
Updates test imports and mocks to provide moveObjectToTrash; primary cleanup test asserts moveObjectToTrash is called and deleteObject is not, verifies metadata/stat clearing on success, and adds a failure case where move rejection causes deleteIt(...) to reject and skips metadata updates.
Demo app cleanup SQL updates
supabase/tests/41_test_demo_app_cleanup.sql
Changes demo-app cleanup assertions to expect preserved demo data when production indicators exist: public.app_versions now expected to have 3 versions including 1.0.0, and related demo tables assertions updated from 0 to 1 row each.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

"I hop and I nudge a file to rest,
Soft in the trash where it can digest,
Seven days of pondering under the moon,
No abrupt endings — a gentler tune. 🐇"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: moving R2 deletes to a delayed trash prefix instead of direct deletion.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description is well-structured with clear summaries, motivation, business impact, and comprehensive test plan.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/r2-delayed-delete-safety

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

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq Bot commented May 19, 2026

Merging this PR will not alter performance

✅ 43 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing codex/r2-delayed-delete-safety (a423a32) with main (7632d62)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@riderx riderx force-pushed the codex/r2-delayed-delete-safety branch from 6fb1cb8 to 352ab9c Compare May 19, 2026 08:19
@riderx riderx force-pushed the codex/r2-delayed-delete-safety branch from 352ab9c to d65207d Compare May 19, 2026 08:33
@riderx riderx force-pushed the codex/r2-delayed-delete-safety branch from d65207d to dd92f62 Compare May 19, 2026 08:41
@riderx riderx marked this pull request as ready for review May 19, 2026 08:50
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Copy link
Copy Markdown
Contributor

@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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
supabase/functions/_backend/triggers/on_version_update.ts (2)

212-261: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Promise.all short-circuit can skip post-delete counter maintenance.

At Line 260, any single move failure rejects Promise.all, and control jumps to the outer catch. That prevents the counter updates below from running, leaving app_versions.manifest_count / apps.manifest_bundle_count stale after partial success.

💡 Suggested fix
-      await Promise.all(promisesMoveS3)
+      const moveResults = await Promise.allSettled(promisesMoveS3)
+      for (const result of moveResults) {
+        if (result.status === 'rejected') {
+          cloudlog({
+            requestId: c.get('requestId'),
+            message: 'manifest delayed-delete move failed',
+            error: result.reason,
+          })
+        }
+      }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@supabase/functions/_backend/triggers/on_version_update.ts` around lines 212 -
261, The Promise.all(promisesMoveS3) call can reject early if any move fails,
skipping the subsequent counter maintenance; change the approach so individual
S3 move failures do not short-circuit the batch: either use
Promise.allSettled(promisesMoveS3) and inspect results or wrap each promise
produced in promisesMoveS3 with a .catch(...) that returns a failure marker
instead of throwing. Keep the rest of the flow intact (the loop building
promisesMoveS3, the supabaseAdmin delete/select logic and the
s3.moveObjectToDelayedDelete call), then after awaiting the settled results
handle/log any per-entry failures but always proceed to update
app_versions.manifest_count and apps.manifest_bundle_count.

222-251: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Replace chained .then() flow with async/await in the manifest cleanup loop.

Lines 43–77 contain nested .then() chains that violate the coding guideline "Always use async/await instead of promises with .then() chains". Since the deleteManifest() function already uses async/await elsewhere, extracting this promise chain into a separate async helper or refactoring it inline would improve readability and error handling consistency.

Chained `.then()` calls found
.delete()
.eq('id', entry.id)
.then(({ error: deleteError }) => {
  // ...
})
.then((v) => {
  // ...
  return s3.moveObjectToDelayedDelete(c, entry.s3_path)
    .then((moved) => {
      // ...
    })
})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@supabase/functions/_backend/triggers/on_version_update.ts` around lines 222 -
251, Refactor the promise chain that deletes and conditionally cleans up
manifest entries into async/await: replace the .then() chain starting from
supabaseAdmin(c).from('manifest').delete().eq('id', entry.id) with an async
helper or inline async code that awaits the delete result, checks deleteError,
then awaits supabaseAdmin(c).from('manifest').select(...).eq(...).maybeSingle()
to verify other references, logs via cloudlog on errors, and if no other
references awaits s3.moveObjectToDelayedDelete(c, entry.s3_path); preserve
current early-return behavior on errors and keep using entry, cloudlog,
supabaseAdmin, and s3.moveObjectToDelayedDelete for identification.
scripts/r2_cleanup/2_delete_orphans.ts (1)

139-189: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

DRY_RUN=false still won't switch this script into live mode.

This file never reads process.env.DRY_RUN, so the documented non-interactive command still starts in dry-run mode and blocks on the confirmation prompt.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/r2_cleanup/2_delete_orphans.ts` around lines 139 - 189, The script
never reads process.env.DRY_RUN so the non-interactive flag is ignored; before
the initial logs (where Mode is printed) initialize DRY_RUN from
process.env.DRY_RUN (e.g., treat "1"/"true" as true and "0"/"false" as false)
and also support a separate non-interactive indicator (like
process.env.NON_INTERACTIVE) so that when DRY_RUN is false and non-interactive
is true the interactive confirmation loop (the for await (const line of console)
{ ... } block) is skipped and the script proceeds in live mode; update the Mode
printout to reflect the resolved DRY_RUN value and ensure string-to-boolean
parsing is used so values like "false" actually become false.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/check_r2_big_files.ts`:
- Around line 1566-1580: The code calls moveR2ObjectsToDelayedDelete once per
file which defeats its internal parallelism and causes thousands of individual
copy/delete cycles; change the logic to batch keys from toDelete into reasonably
sized chunks (e.g., 100–1000 keys) and call moveR2ObjectsToDelayedDelete(s3,
S3_BUCKET, keysBatch) per batch, await Promise.all over the batch-level
promises, aggregate each batch's results, and update progress/logging using the
batch results; reference moveR2ObjectsToDelayedDelete, toDelete, s3, and
S3_BUCKET when making the replacement and ensure error logging inspects each
returned result.error for individual keys.

In `@scripts/cleanup_s3_folder.ts`:
- Around line 29-37: The movedCount is incremented even if s3client.deleteObject
fails; update the move loop (the block calling s3client.copyObject,
s3client.deleteObject, delayedDeleteKey and referencing obj.key and movedCount)
so that you only increment movedCount after the delete completes
successfully—e.g., await the delete inside a try/catch and increment movedCount
in the success path, and log or handle the error in the catch without increasing
movedCount.

In `@scripts/r2_cleanup/2_delete_orphans.ts`:
- Around line 121-134: The current outer Promise.all over batchGroup multiplies
internal fan-out in moveR2ObjectsToDelayedDelete and causes tens of thousands of
concurrent copy requests; change the outer loop to avoid parallelizing those
calls — either iterate batchGroup serially (for...of await) or pass a
concurrency limit into moveR2ObjectsToDelayedDelete and call it one-at-a-time
instead of Promise.all(batchGroup.map(...)); update the block using CONCURRENCY
and the call to moveR2ObjectsToDelayedDelete so only the internal per-key
concurrency is active (or reduce the outer CONCURRENCY to 1) and ensure
totalMoved/totalErrors are updated the same way.

In `@supabase/functions/_backend/files/retry.ts`:
- Around line 87-92: When copying an object to the delayed-delete prefix in
retry.ts, preserve its httpMetadata so headers like content-type/cache-control
are retained: after retrieving the R2ObjectBody via this.get(key) (variable
object), pass object.httpMetadata as the fourth argument to
this.put(buildDelayedDeleteKey(key), await object.arrayBuffer(), undefined,
object.httpMetadata) instead of only uploading the raw bytes; ensure the same
httpMetadata is used when calling this.put so later restores retain original
HTTP metadata, then proceed to this.delete(key).

In `@tests/on-version-update-cleanup.unit.test.ts`:
- Line 107: Update the two tests in tests/on-version-update-cleanup.unit.test.ts
that currently use it(...) to it.concurrent(...); specifically replace the it
call for "moves the bundle to delayed delete and clears stored size for
soft-deleted versions" and the other test at the second location (line with the
same pattern) so both use Jest's it.concurrent to allow parallel execution
within the file and comply with the tests/**/*.test.ts guideline.

In `@tests/r2-delayed-delete.unit.test.ts`:
- Line 5: The test suite title in the describe call currently uses uppercase
("R2 delayed delete"), which violates the test/prefer-lowercase-title lint rule;
update the describe(...) invocation (the suite defined by describe('R2 delayed
delete', ...) in tests/r2-delayed-delete.unit.test.ts) to use a fully lowercase
string (e.g., "r2 delayed delete") so the suite title passes the linter.

---

Outside diff comments:
In `@scripts/r2_cleanup/2_delete_orphans.ts`:
- Around line 139-189: The script never reads process.env.DRY_RUN so the
non-interactive flag is ignored; before the initial logs (where Mode is printed)
initialize DRY_RUN from process.env.DRY_RUN (e.g., treat "1"/"true" as true and
"0"/"false" as false) and also support a separate non-interactive indicator
(like process.env.NON_INTERACTIVE) so that when DRY_RUN is false and
non-interactive is true the interactive confirmation loop (the for await (const
line of console) { ... } block) is skipped and the script proceeds in live mode;
update the Mode printout to reflect the resolved DRY_RUN value and ensure
string-to-boolean parsing is used so values like "false" actually become false.

In `@supabase/functions/_backend/triggers/on_version_update.ts`:
- Around line 212-261: The Promise.all(promisesMoveS3) call can reject early if
any move fails, skipping the subsequent counter maintenance; change the approach
so individual S3 move failures do not short-circuit the batch: either use
Promise.allSettled(promisesMoveS3) and inspect results or wrap each promise
produced in promisesMoveS3 with a .catch(...) that returns a failure marker
instead of throwing. Keep the rest of the flow intact (the loop building
promisesMoveS3, the supabaseAdmin delete/select logic and the
s3.moveObjectToDelayedDelete call), then after awaiting the settled results
handle/log any per-entry failures but always proceed to update
app_versions.manifest_count and apps.manifest_bundle_count.
- Around line 222-251: Refactor the promise chain that deletes and conditionally
cleans up manifest entries into async/await: replace the .then() chain starting
from supabaseAdmin(c).from('manifest').delete().eq('id', entry.id) with an async
helper or inline async code that awaits the delete result, checks deleteError,
then awaits supabaseAdmin(c).from('manifest').select(...).eq(...).maybeSingle()
to verify other references, logs via cloudlog on errors, and if no other
references awaits s3.moveObjectToDelayedDelete(c, entry.s3_path); preserve
current early-return behavior on errors and keep using entry, cloudlog,
supabaseAdmin, and s3.moveObjectToDelayedDelete for identification.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 636648de-2945-4673-ab34-76a2adc5d8a0

📥 Commits

Reviewing files that changed from the base of the PR and between 59cc08e and dd92f62.

📒 Files selected for processing (22)
  • cli/src/types/supabase.types.ts
  • package.json
  • scripts/change_app_owner.ts
  • scripts/check_r2.ts
  • scripts/check_r2_big_files.ts
  • scripts/cleanup_s3_folder.ts
  • scripts/r2_cleanup/2_delete_orphans.ts
  • scripts/r2_cleanup/README.md
  • scripts/r2_delayed_delete.ts
  • src/types/supabase.types.ts
  • supabase/functions/_backend/files/retry.ts
  • supabase/functions/_backend/files/uploadHandler.ts
  • supabase/functions/_backend/files/util.ts
  • supabase/functions/_backend/public/app/delete.ts
  • supabase/functions/_backend/triggers/on_app_delete.ts
  • supabase/functions/_backend/triggers/on_version_update.ts
  • supabase/functions/_backend/utils/r2_delayed_delete.ts
  • supabase/functions/_backend/utils/s3.ts
  • supabase/functions/_backend/utils/supabase.types.ts
  • supabase/tests/41_test_demo_app_cleanup.sql
  • tests/on-version-update-cleanup.unit.test.ts
  • tests/r2-delayed-delete.unit.test.ts

Comment thread scripts/check_r2_big_files.ts Outdated
Comment thread scripts/cleanup_s3_folder.ts Outdated
Comment thread scripts/r2_cleanup/2_delete_orphans.ts Outdated
Comment thread supabase/functions/_backend/files/retry.ts Outdated
Comment thread tests/on-version-update-cleanup.unit.test.ts Outdated
Comment thread tests/r2-delayed-delete.unit.test.ts Outdated
@riderx riderx force-pushed the codex/r2-delayed-delete-safety branch from dd92f62 to ebfe734 Compare May 19, 2026 09:04
Copy link
Copy Markdown
Contributor

@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 (2)
scripts/r2_cleanup/2_delete_orphans.ts (1)

208-237: 💤 Low value

Dead code: DRY_RUN check after main work is unreachable.

Lines 235-236 check if (DRY_RUN) after all the move operations complete, but this branch is never reached. When DRY_RUN is true after the interactive prompt (or when NON_INTERACTIVE=true with default DRY_RUN=true), the function returns early at line 205. Consider removing this dead code for clarity.

♻️ Suggested cleanup
   console.log(`\n\n=== Done ===`)
   console.log(`Total moved: ${totalMoved}`)
   console.log(`Errors: ${totalErrors}`)
-  if (DRY_RUN)
-    console.log(`\n(DRY RUN - nothing actually moved)`)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/r2_cleanup/2_delete_orphans.ts` around lines 208 - 237, The final
DRY_RUN conditional after the main work is dead/unreachable because the function
returns early when DRY_RUN is true (see the early return around line 205 where
DRY_RUN is handled); remove the unreachable check block that logs "(DRY RUN -
nothing actually moved)" at the end of the function to clean up dead code.
Locate the end of the async routine that calls moveFiles(...) and
streamMove(...) and delete the if (DRY_RUN) console.log(...) branch (or
consolidate DRY_RUN-related messaging into the earlier prompt/early-return path)
so only reachable DRY_RUN logging remains.
tests/on-version-update-cleanup.unit.test.ts (1)

28-46: 💤 Low value

Unused deleteObject mock can be removed.

Line 34 still defines deleteObject: vi.fn() in the hoisted return, but it's no longer exported or used anywhere in the test file after switching to moveObjectToDelayedDelete.

♻️ Suggested cleanup
   return {
     appVersionsMetaSelectEq,
     appVersionsMetaUpdate,
     appVersionsMetaUpdateEq,
     closeClient: vi.fn(),
     createStatsMeta: vi.fn(),
-    deleteObject: vi.fn(),
     getDrizzleClient: vi.fn(() => ({
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/on-version-update-cleanup.unit.test.ts` around lines 28 - 46, Remove
the unused mock entry deleteObject: vi.fn() from the hoisted mocked return
object so only active mocks remain (e.g., keep moveObjectToDelayedDelete,
supabaseAdmin, getDrizzleClient, etc.); search for any remaining references to
deleteObject in the test to ensure it's not used elsewhere and update the mocked
return accordingly in the setup function (the object that currently contains
appVersionsMetaSelectEq, appVersionsMetaUpdate, appVersionsMetaUpdateEq,
closeClient, createStatsMeta, getDrizzleClient, getPgClient,
moveObjectToDelayedDelete, supabaseAdmin, supabaseFrom).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@scripts/r2_cleanup/2_delete_orphans.ts`:
- Around line 208-237: The final DRY_RUN conditional after the main work is
dead/unreachable because the function returns early when DRY_RUN is true (see
the early return around line 205 where DRY_RUN is handled); remove the
unreachable check block that logs "(DRY RUN - nothing actually moved)" at the
end of the function to clean up dead code. Locate the end of the async routine
that calls moveFiles(...) and streamMove(...) and delete the if (DRY_RUN)
console.log(...) branch (or consolidate DRY_RUN-related messaging into the
earlier prompt/early-return path) so only reachable DRY_RUN logging remains.

In `@tests/on-version-update-cleanup.unit.test.ts`:
- Around line 28-46: Remove the unused mock entry deleteObject: vi.fn() from the
hoisted mocked return object so only active mocks remain (e.g., keep
moveObjectToDelayedDelete, supabaseAdmin, getDrizzleClient, etc.); search for
any remaining references to deleteObject in the test to ensure it's not used
elsewhere and update the mocked return accordingly in the setup function (the
object that currently contains appVersionsMetaSelectEq, appVersionsMetaUpdate,
appVersionsMetaUpdateEq, closeClient, createStatsMeta, getDrizzleClient,
getPgClient, moveObjectToDelayedDelete, supabaseAdmin, supabaseFrom).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2033fbac-55f2-4a58-a0bd-1ab4ed91b5f7

📥 Commits

Reviewing files that changed from the base of the PR and between dd92f62 and ebfe734.

📒 Files selected for processing (22)
  • cli/src/types/supabase.types.ts
  • package.json
  • scripts/change_app_owner.ts
  • scripts/check_r2.ts
  • scripts/check_r2_big_files.ts
  • scripts/cleanup_s3_folder.ts
  • scripts/r2_cleanup/2_delete_orphans.ts
  • scripts/r2_cleanup/README.md
  • scripts/r2_delayed_delete.ts
  • src/types/supabase.types.ts
  • supabase/functions/_backend/files/retry.ts
  • supabase/functions/_backend/files/uploadHandler.ts
  • supabase/functions/_backend/files/util.ts
  • supabase/functions/_backend/public/app/delete.ts
  • supabase/functions/_backend/triggers/on_app_delete.ts
  • supabase/functions/_backend/triggers/on_version_update.ts
  • supabase/functions/_backend/utils/r2_delayed_delete.ts
  • supabase/functions/_backend/utils/s3.ts
  • supabase/functions/_backend/utils/supabase.types.ts
  • supabase/tests/41_test_demo_app_cleanup.sql
  • tests/on-version-update-cleanup.unit.test.ts
  • tests/r2-delayed-delete.unit.test.ts
✅ Files skipped from review due to trivial changes (2)
  • supabase/functions/_backend/files/util.ts
  • scripts/r2_cleanup/README.md
🚧 Files skipped from review as they are similar to previous changes (16)
  • supabase/functions/_backend/files/uploadHandler.ts
  • package.json
  • supabase/functions/_backend/triggers/on_app_delete.ts
  • tests/r2-delayed-delete.unit.test.ts
  • supabase/functions/_backend/public/app/delete.ts
  • scripts/cleanup_s3_folder.ts
  • supabase/functions/_backend/files/retry.ts
  • supabase/functions/_backend/utils/supabase.types.ts
  • cli/src/types/supabase.types.ts
  • supabase/functions/_backend/utils/r2_delayed_delete.ts
  • src/types/supabase.types.ts
  • scripts/change_app_owner.ts
  • scripts/check_r2_big_files.ts
  • supabase/functions/_backend/utils/s3.ts
  • supabase/tests/41_test_demo_app_cleanup.sql
  • scripts/r2_delayed_delete.ts

@riderx riderx force-pushed the codex/r2-delayed-delete-safety branch from ebfe734 to 062724d Compare May 19, 2026 11:35
Copy link
Copy Markdown
Contributor

@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.

♻️ Duplicate comments (1)
tests/on-version-update-cleanup.unit.test.ts (1)

110-110: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use it.concurrent(...) for these updated tests.

These changed cases still use it(...); switch them to it.concurrent(...) to match the test parallelization rule.

Suggested patch
-  it('moves the bundle to trash and clears stored size for soft-deleted versions', async () => {
+  it.concurrent('moves the bundle to trash and clears stored size for soft-deleted versions', async () => {
@@
-  it('still clears stale metadata when the deleted version has no bundle path', async () => {
+  it.concurrent('still clears stale metadata when the deleted version has no bundle path', async () => {
@@
-  it('keeps the queue retryable when moving the bundle to trash fails', async () => {
+  it.concurrent('keeps the queue retryable when moving the bundle to trash fails', async () => {

As per coding guidelines tests/**/*.test.ts: "Design all tests for parallel execution across files; use it.concurrent() instead of it() to run tests in parallel within the same file for faster CI/CD".

Also applies to: 121-121, 130-130

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/on-version-update-cleanup.unit.test.ts` at line 110, Replace the
synchronous test declarations with parallelized ones by changing it(...) to
it.concurrent(...) for the affected test cases (for example the test with
description "moves the bundle to trash and clears stored size for soft-deleted
versions" and the other two updated tests mentioned in the comment). Locate the
three it(...) blocks in the same test file and update each to it.concurrent(...)
so the tests run in parallel within the file; no other logic changes are needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@tests/on-version-update-cleanup.unit.test.ts`:
- Line 110: Replace the synchronous test declarations with parallelized ones by
changing it(...) to it.concurrent(...) for the affected test cases (for example
the test with description "moves the bundle to trash and clears stored size for
soft-deleted versions" and the other two updated tests mentioned in the
comment). Locate the three it(...) blocks in the same test file and update each
to it.concurrent(...) so the tests run in parallel within the file; no other
logic changes are needed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d6860b36-d6c0-4186-b206-0a0fcedab9b6

📥 Commits

Reviewing files that changed from the base of the PR and between ebfe734 and 062724d.

📒 Files selected for processing (3)
  • supabase/functions/_backend/triggers/on_version_update.ts
  • supabase/functions/_backend/utils/s3.ts
  • tests/on-version-update-cleanup.unit.test.ts

@riderx riderx force-pushed the codex/r2-delayed-delete-safety branch from 062724d to a95ac7e Compare May 19, 2026 11:43
Copy link
Copy Markdown
Contributor

@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

🧹 Nitpick comments (1)
supabase/tests/41_test_demo_app_cleanup.sql (1)

3-222: ⚖️ Poor tradeoff

Consider adding negative case test coverage.

This test validates that demo data is preserved when production indicators exist. Consider adding a complementary test that verifies demo data IS cleaned up when production indicators are absent (e.g., no is_prod devices, no production channels). This would fully validate both branches of the production-signal guard.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@supabase/tests/41_test_demo_app_cleanup.sql` around lines 3 - 222, Add a
complementary negative-case test that creates a demo app (similar to id
'11111111-1111-1111-1111-111111111111') but without production indicators (set
devices.is_prod = false / omit production channels, channel_devices,
deploy_history entries, etc.), run the same cleanup flow (use
tests.authenticate_as_service_role() and plan count adjusted), and assert that
rows in app_versions, channels, channel_devices, deploy_history, devices,
daily_mau, daily_bandwidth, daily_storage, daily_version, and build_requests for
that app_id are removed while cached counters on apps (last_version,
channel_device_count, manifest_bundle_count) are reset; mirror the positive
assertions in this file but expect zeros/deletes instead of preserved counts.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@supabase/tests/41_test_demo_app_cleanup.sql`:
- Around line 145-147: The test description is incorrect about what counts as
"production indicators"; update the comment to state that
clear_onboarding_app_data() (migration 20260519065534) treats the presence of
existing data as the guard conditions — specifically: any rows in
public.devices, any rows in public.channel_devices, any deploy_history rows
beyond the preserved version, or any channel with a non-preserved bundle version
— rather than checking an is_prod flag or channel name; change the comment text
to accurately reflect these four data-existence checks as the production
indicators.

---

Nitpick comments:
In `@supabase/tests/41_test_demo_app_cleanup.sql`:
- Around line 3-222: Add a complementary negative-case test that creates a demo
app (similar to id '11111111-1111-1111-1111-111111111111') but without
production indicators (set devices.is_prod = false / omit production channels,
channel_devices, deploy_history entries, etc.), run the same cleanup flow (use
tests.authenticate_as_service_role() and plan count adjusted), and assert that
rows in app_versions, channels, channel_devices, deploy_history, devices,
daily_mau, daily_bandwidth, daily_storage, daily_version, and build_requests for
that app_id are removed while cached counters on apps (last_version,
channel_device_count, manifest_bundle_count) are reset; mirror the positive
assertions in this file but expect zeros/deletes instead of preserved counts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f4b4dd88-b5eb-4438-8520-d3189d343396

📥 Commits

Reviewing files that changed from the base of the PR and between 062724d and a95ac7e.

📒 Files selected for processing (4)
  • supabase/functions/_backend/triggers/on_version_update.ts
  • supabase/functions/_backend/utils/s3.ts
  • supabase/tests/41_test_demo_app_cleanup.sql
  • tests/on-version-update-cleanup.unit.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • supabase/functions/_backend/triggers/on_version_update.ts
  • tests/on-version-update-cleanup.unit.test.ts
  • supabase/functions/_backend/utils/s3.ts

Comment thread supabase/tests/41_test_demo_app_cleanup.sql Outdated
@riderx riderx force-pushed the codex/r2-delayed-delete-safety branch from a95ac7e to 4211496 Compare May 19, 2026 11:54
@riderx riderx force-pushed the codex/r2-delayed-delete-safety branch from 4211496 to a423a32 Compare May 19, 2026 13:00
@sonarqubecloud
Copy link
Copy Markdown

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.

1 participant