Skip to content

perf(cache): reduce response store Durable Object load - #3213

Open
james-elicx wants to merge 17 commits into
codex/response-store-html-identityfrom
codex/response-store-do-efficiency
Open

perf(cache): reduce response store Durable Object load#3213
james-elicx wants to merge 17 commits into
codex/response-store-html-identityfrom
codex/response-store-do-efficiency

Conversation

@james-elicx

@james-elicx james-elicx commented Sep 10, 2026

Copy link
Copy Markdown
Member

Summary

  • collapse successful Response Store writes to one reservation and one publication metadata RPC
  • remove eager per-entry tag index rows; soft-tag expiration is read only after a real candidate hit and memoized for the current request
  • keep pending-object and revalidation rows transient, with successful cleanup folded into existing RPCs and alarm recovery reserved for interrupted work
  • batch bulk refresh reservations, tag invalidations, purge mutations, and cleanup SQL
  • coalesce overlapping framework writes without changing public put ordering

Durable Object load

Counts compare this PR with its parent. They count metadata Durable Object RPCs, not the service-binding or Workers Cache request. R2 operations are unchanged unless stated.

Scenario Before After
cache bypass or non-GET/HEAD route 0 0
Workers Cache hit 0 0
Workers Cache miss with no durable entry 1 1
Workers Cache miss refilled from R2 1 1
cold cacheable route miss plus successful fill 7 3
successful new put() 6 2
successful replacement put() 8 2
coalesced overlapping put() follower n/a 2
successful stale revalidation 9 3
stale revalidation that loses the claim 2 2
failed stale revalidation 3 3
hard-expired or missing-body regeneration 8 3
refresh of N matched entries 1 + 7N 1 + N
purge matching at least one entry 3 1
soft-tag expiration, per distinct tag set in one request 1 1

The soft-tag lookup is deliberately unchanged from the authoritative design: misses, entries without soft tags, and repeated uses of the same tag set in one request perform zero expiration RPCs. A candidate hit with a new tag set performs one RPC; it is not replaced with synthetic HTTP or Workers Cache marker entries.

SQLite load

T is the number of tags on a newly published entry or tag selectors in a purge, N is the number of entries matched by a bulk mutation, C is the number of live claims removed, and S is the number of old per-entry tag-index rows. Statement counts exclude schema initialization and alarm storage API calls. Row counts are logical table rows and exclude index maintenance.

Operation Before SQL statements After SQL statements Logical rows written before Logical rows written after
new put 9 + T 6 4 + T 4
replacement put 11 + T 8 6 + old tags + T 6
successful stale revalidation 14 + T 11 8 + old tags + T 8
refresh N entries 1 + N(10 + T) 1 + ceil(N/100) + ceil(N/50) + 5N 6N + old/new tag rows 6N
purge N entries with T selectors 1 + T + 5N 1 + ceil(T/50) + 3ceil(N/99) + ceil(N/100), plus 1 for tag purges 3N + C + S + T 3N + C + T, plus 1 for tag purges

Successful operations leave zero pending_objects and zero completed revalidation_claims; E2E tests inspect SQLite directly to enforce that invariant. The steady durable footprint is one entries row per cache key, one tag_invalidations row per tag that has actually been invalidated, and one metadata sequence row and two schema migration marker rows. There are no persistent per-entry tag-index rows.

A publication normally performs no tag SQL read. If a tag purge completes between reservation and publication, the existing publication RPC performs up to ceil(T/100) additional read-only SQL statements to determine whether the candidate's own tags were invalidated. This adds no metadata RPC and keeps tag checks off cache reads and ordinary publications.

The first reservation after a Durable Object activation checks the cleanup alarm and creates it if absent; later reservations while that actor remains active do not repeat that storage call. The alarm uses one SQL query per sweep and only handles interrupted or superseded candidates, replacing the old background cleanup RPC on every put.

For 10,000 warmups that seed one cache entry, metadata load falls from about 70,000 DO requests to 30,000. With four tags per entry, logical SQLite writes fall from about 80,000 to 40,000. An App Router warmup that seeds both HTML and RSC from one request falls from about 130,000 to 50,000 DO requests and from about 160,000 to 80,000 logical SQLite writes for 10,000 routes.

Validation

  • pnpm --filter @vinext/workers-response-store test (43 tests)
  • pnpm exec vitest run tests/response-store-adapter.e2e.test.ts tests/response-store-data.test.ts from packages/cloudflare (29 tests)
  • targeted formatting, lint, type checks, dry-run Worker builds, and direct SQLite cleanup assertions

@pkg-pr-new

pkg-pr-new Bot commented Sep 10, 2026

Copy link
Copy Markdown

Open in StackBlitz

@vinext/cloudflare

npm i https://pkg.pr.new/cloudflare/vinext/@vinext/cloudflare@3213

create-vinext-app

npm i https://pkg.pr.new/cloudflare/vinext/create-vinext-app@3213

@vinext/types

npm i https://pkg.pr.new/cloudflare/vinext/@vinext/types@3213

vinext

npm i https://pkg.pr.new/cloudflare/vinext@3213

@vinext/workers-response-store

npm i https://pkg.pr.new/cloudflare/vinext/@vinext/workers-response-store@3213

commit: 70dae5c

@github-actions

Copy link
Copy Markdown
Contributor
Example Preview Production Original
app-router-cloudflare preview production
pages-router-cloudflare preview production
app-router-playground preview production original
realworld-api-rest preview production
nextra-docs-template preview production
benchmarks preview production
hackernews preview production original
workers-cache preview production
static-export preview production
web preview production

@github-actions

github-actions Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Performance benchmarks

Compared b9b4ca6 against base 13cbd88 using alternating same-runner rounds. Next.js was unchanged and skipped.

0 improved · 0 regressed · 6 within ±1.5%

Scenario Framework Baseline Current Change
Client bundle size (gzip) vinext 142.2 KB 142.2 KB ⚫ -0.0%
Client entry size (gzip) vinext 129.6 KB 129.6 KB ⚫ -0.0%
Dev server cold start vinext 3.32 s 3.31 s ⚫ -0.5%
Production build time vinext 3.72 s 3.72 s ⚫ -0.1%
RSC entry closure size (gzip) vinext 125.8 KB 125.8 KB ⚫ +0.0%
Server bundle size (gzip) vinext 216.5 KB 216.5 KB ⚫ +0.0%

View detailed results and traces

🟢 improvement · 🔴 regression · ⚫ change below 1.5% · paired base/head

@james-elicx
james-elicx force-pushed the codex/response-store-do-efficiency branch 2 times, most recently from fd3a4a1 to 77dc071 Compare September 13, 2026 21:18
@james-elicx
james-elicx force-pushed the codex/response-store-do-efficiency branch from 77dc071 to 8cbf30a Compare September 13, 2026 21:20
@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

Please prioritize correctness issues in the latest Response Store DO changes and return findings or an explicit no-findings verdict before the 10-minute review limit expires.

@james-elicx
james-elicx marked this pull request as ready for review September 13, 2026 21:55
@cloudflare cloudflare deleted a comment from ask-bonk Bot Sep 13, 2026
@ask-bonk

ask-bonk Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

@james-elicx Bonk workflow was cancelled.

View workflow run · To retry, trigger Bonk again.

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

Hard deadline: 8 minutes. Stop investigation early enough to post either prioritized actionable findings or an explicit no-findings verdict before 8 minutes elapse.

Comment thread packages/workers-response-store/src/metadata-do.ts
@ask-bonk

ask-bonk Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Posted 1 actionable inline finding.

github run

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

Hard deadline: 8 minutes. Stop investigation early enough to post either prioritized actionable findings or an explicit no-findings verdict before 8 minutes elapse.

Comment thread packages/workers-response-store/src/metadata-do.ts
@ask-bonk

ask-bonk Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Posted 1 actionable inline finding.

github run

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

Re-review exact head c09351b after addressing your schema migration finding. Hard deadline: 8 minutes. Stop early enough to post actionable findings or an explicit no-findings verdict.

reservation ??= await this.reserveWrite(metadata, keyHash, cacheKey, cacheTags);
try {
const result = await pending;
if (result.backingStoreUpdated) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P1] Honor the follower's purgeExisting request. This returns the leader's result unchanged, so if the leader used purgeExisting: false and this coalesced follower uses true, no edge purge runs even though the follower reports edgePurgeAccepted: true. The backing write can therefore succeed while an older edge response remains served. Before returning, apply this invocation's purge requirement (or only coalesce writes with compatible purge options), and cover the mixed-options case.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in d8c6587. Coalescing is now partitioned by purgeExisting semantics, so a purging follower cannot inherit a non-purging leader result. The mixed-options regression passes, and the package suite is 39/39.

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

Re-review exact head 3958d51 after addressing the follower cleanup failure finding. Hard deadline: 8 minutes. Stop early enough to post actionable findings or an explicit no-findings verdict.

await metadata
.finishPendingObjects([objectKey])
.catch((error) => this.logCleanupFailure(objectKey, error));
await response.body?.cancel().catch(() => {});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P1] Do not await cancellation of a coalesced follower body. The adapter passes cacheBody, one branch of rendered.body.tee(), into put() and does not return the sibling foreground branch until put() resolves. A tee branch’s cancel() promise waits for the sibling/source to finish, so an overlapping warmup that follows another write can deadlock here: put() waits for cancellation while the caller cannot consume the foreground branch. Start cancellation without awaiting it (while handling rejection), and cover coalescing with a tee-backed response whose sibling is consumed only after put() returns.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in e528112. Coalesced follower body cancellation is now fire-and-forget with rejection handled, and the tee-backed regression consumes the sibling only after put() returns.

await response.body?.cancel().catch(() => {});
return result;
}
} catch {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P2] Distinguish edge-purge rejection from backing-write failure. pending includes the purge at line 669, so it can reject after the leader has already published successfully. This catch then falls through to storeResponse() and writes the follower as another revision, reproducing a duplicate write on a different post-commit error path. Separate the backing publication result from edge purging, or propagate the leader’s purge failure after disposing of the follower body without rewriting; add a coalesced purgeExisting test where ctx.cache.purge() rejects.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in e528112. pendingPuts now shares only the backing publication promise; edge purge runs after that boundary, so a purge rejection propagates without entering the fallback write path.

@ask-bonk

ask-bonk Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor
  • P1 metadata-do.ts:597: An expired revalidation claim can publish after a newer claim replaces it because claimId is not validated. Require publication to match the active claim and revision.

Posted 2 additional actionable inline findings.

github run

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

Re-review exact head e528112 after addressing all three latest findings: tee cancellation, post-publication purge failures, and stale claim publication. Hard deadline: 8 minutes. Stop early enough to post actionable findings or an explicit no-findings verdict.

return {
backingStoreUpdated: true,
edgePurgeAccepted: options.purgeExisting
? await this.purgeEdgeCacheByTags([purgeTagForEntry(result.entry)])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P2] Keep this purge rejection out of the fallback-write catch. The try still encloses the follower purgeEdgeCacheByTags() call, so if that purge rejects after pending has confirmed publication, line 651 catches it as a leader failure and falls through to storeResponse(). At that point cancellation of this response body has also already started, so the fallback can either publish another revision or fail while reading a cancelled stream. Dispose of the follower body and perform/return the purge outside the catch that handles pending rejection; add the requested coalesced purging-follower test with ctx.cache.purge() rejecting.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 9a5fbea. The catch now encloses only await pending; all follower cleanup, cancellation, and edge-purge work runs after it, so purge rejection cannot enter the fallback-write path. I also replaced a fixed sleep in the purge/write race regression with an observed reservation barrier. Package tests pass 41/41.

@ask-bonk

ask-bonk Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Posted 1 actionable inline finding.

github run

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

Re-review exact head 9a5fbea after moving follower purge fully outside the fallback-write catch. Hard deadline: 8 minutes. Stop early enough to post actionable findings or an explicit no-findings verdict.

publication = await metadata.publish(keyHash, revision, candidate, write.claimId);
} catch (error) {
await this.deletePendingObjects(metadata, [objectKey]);
await this.releaseFailedWrite(metadata, write);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P2] Do not classify post-publication alarm failures as write failures. metadata.publish() commits its SQL transaction before awaiting ensureCleanupAlarm() (metadata-do.ts:670). After a DO reactivation, that getAlarm()/setAlarm() call can reject even though this revision is already active; this catch then releases/reports the committed write as failed, and a coalesced follower can fall back to publishing another revision. Keep post-commit alarm maintenance non-throwing (the reservation already established recovery), or otherwise reconcile the publication before propagating the error, and cover a post-commit alarm-storage rejection.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in d8f4afa. Post-commit cleanup-alarm maintenance is now caught and logged before immediate R2 cleanup continues, so an alarm API failure cannot make a committed publication appear failed or trigger a follower rewrite. Package tests pass 41/41.

@ask-bonk

ask-bonk Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Posted 1 actionable inline finding.

github run

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

Re-review exact head d8f4afa after isolating post-commit cleanup-alarm failures. Hard deadline: 8 minutes. Stop early enough to post actionable findings or an explicit no-findings verdict.

);
});
if (matches.length) {
await this.ensureCleanupAlarm(invalidatedAt);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P1] Keep post-commit alarm failures from aborting selective purge. The transaction above has already tombstoned these entries and queued their old objects, but if ensureCleanupAlarm() rejects here, this method skips deleteTrackedObjects() and throws back to binding.purge() before the edge purge runs. Retrying cannot recover that edge invalidation because findMatchingEntryRows(..., true) no longer selects the tombstone (active_revision === latest_revision), so the old Workers Cache response can continue to be served. Catch/log this alarm error as publish() now does and continue the immediate R2/edge cleanup; please cover an alarm-storage rejection after the purge transaction commits.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in ff28872. Both publication and purge now use the same non-throwing post-commit cleanup-alarm maintenance helper, so selective purge continues through immediate R2 deletion and edge invalidation after an alarm API failure. Package tests pass 41/41.

@ask-bonk

ask-bonk Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Posted 1 actionable inline finding.

github run

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

Re-review exact head ff28872 after making post-commit alarm maintenance non-throwing for selective purge. Hard deadline: 8 minutes. Stop early enough to post actionable findings or an explicit no-findings verdict.

revision > current.latest_revision ||
(current.active_revision !== null && revision <= current.active_revision) ||
(claimId !== undefined &&
(current.claim_id !== claimId || current.claim_revision !== revision)) ||

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P1] Reject claims for a superseded active revision. This checks the claim ID and reserved revision, but not revalidation_claims.active_revision. A normal write can reserve revision 2, a stale read can then claim revision 3 against active revision 1, and revision 2 can publish before the revalidation completes. The revision-3 claim still passes these checks and overwrites the newer response even though it regenerated revision 1. Select the claim active revision and require it to equal the entry current active revision; add this interleaving as a regression test.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 58975dd. Publication now requires the claim source active revision to equal the entry current active revision; a regression covers a normal write publishing between claim and regeneration.

.map(({ object_key }) => object_key);
this.finishPendingObjects(activeObjectKeys);
if (expiredObjectKeys.length) {
await this.env.CACHE_BODIES.delete(expiredObjectKeys);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P1] Invalidate an expired reservation before awaiting its R2 deletion. The expired marker remains publishable while this await yields. If a long-running write reaches R2 around the retention deadline, the sweep can select its marker, then the write can publish that same object while deletion is in flight; the sweep subsequently deletes the now-active body and removes its marker, leaving metadata that serves a permanently missing object. Atomically claim/remove the expired reservation before the R2 await so publish() rejects it (and restore/retain cleanup state if deletion fails), with a race test covering publication during the delete.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 58975dd. Expired reservations are removed before the R2 await so publication rejects during deletion; a failed deletion atomically restores the original markers and invalidation sequences for retry.

)
.toArray();
private async maintainCleanupAlarm(createdAt: number): Promise<void> {
await this.ensureCleanupAlarm(createdAt).catch((error) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P2] Preserve a retry when alarm maintenance and immediate deletion both fail. Swallowing this error lets purge/publication continue, but deleteTrackedObjects() also catches R2 deletion failures. If this was a setAlarm() failure with no existing alarm and R2 deletion then fails, the pending marker is left with nothing scheduled to retry it; with no later write, the old object leaks indefinitely. Record the alarm failure and ensure the R2 failure path retries alarm scheduling (or otherwise retains a guaranteed cleanup trigger).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 58975dd. Any immediate R2 cleanup failure now schedules an alarm retry; if scheduling itself fails, the pending marker remains durable and the failure is logged for a later activation/write to recover.

@ask-bonk

ask-bonk Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Posted 3 actionable inline findings.

github run

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

Re-review exact head 58975dd after addressing claim source revision, cleanup deletion race, and cleanup retry scheduling. Hard deadline: 8 minutes. Stop early enough to post actionable findings or an explicit no-findings verdict.

.map(({ object_key }) => object_key);
const expired = batch.filter(({ active, created_at }) => !active && created_at <= cutoff);
const expiredObjectKeys = expired.map(({ object_key }) => object_key);
this.finishPendingObjects([...activeObjectKeys, ...expiredObjectKeys]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P1] Keep a durable cleanup record while the R2 deletion is in flight. This commits removal of the only pending_objects row before the external await. If the DO is restarted or the event is terminated while CACHE_BODIES.delete() is pending, the catch never runs to restore the row, and the next alarm has no record from which to retry, so the object leaks permanently. The same gap leaves no marker if a fenced writer uploads just after the delete and its subsequent best-effort cleanup fails. Fence publication with durable cleanup state, but retain a discoverable retry record until R2 deletion has completed; cover interruption between this transaction and completion of the delete.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in b9b4ca6. pending_objects now has durable publishable vs cleanup state. Expired reservations are atomically made non-publishable but retained through the R2 await; success removes them, interruption or failure leaves a discoverable retry record. Failed best-effort cleanup also upserts that durable record.

}),
);
try {
await this.ctx.storage.setAlarm(Date.now() + ORPHAN_CLEANUP_RETRY_MS);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P2] Make the retry alarm actually select these failed deletions. Replacement/purge cleanup markers retain their current created_at, so this alarm fires in one minute but sweepExpiredPendingObjects() uses the one-hour retention cutoff and skips them; it then schedules the next alarm for created_at + ORPHAN_RETENTION_MS. A transient immediate R2 failure therefore is not retried after ORPHAN_CLEANUP_RETRY_MS and leaves the old body for roughly an hour. Mark failed cleanup rows as immediately retryable (or distinguish cleanup retries from live reservations) before scheduling this alarm, and test the alarm one retry interval after a failed immediate deletion.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in b9b4ca6. Failed immediate deletions now upsert non-publishable cleanup rows with created_at=0 before scheduling the one-minute retry, so the next sweep selects them immediately instead of waiting for retention expiry.

@ask-bonk

ask-bonk Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Posted 2 actionable inline findings.

github run

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

Re-review exact head b9b4ca6 after adding durable non-publishable cleanup state and immediate retry eligibility. Hard deadline: 8 minutes. Stop early enough to post actionable findings or an explicit no-findings verdict.

}
if (expiredObjectKeys.length) {
await this.env.CACHE_BODIES.delete(expiredObjectKeys);
this.finishPendingObjects(expiredObjectKeys);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P1] Retain the cleanup tombstone past this first delete. A writer can already be inside CACHE_BODIES.put() when the sweep flips its reservation to non-publishable. If this delete finishes first, this line removes the only durable record; the older put can then complete and recreate the object. Its later publish() is correctly rejected, but that path does not establish a cleanup row before awaiting its best-effort delete, so termination before/during that delete (or termination before publish() altogether) leaks the recreated object permanently. Keep a discoverable non-publishable marker through a follow-up cleanup phase that is safely after the fenced writer, rather than deleting it immediately after the first R2 delete, and cover a put that completes between this delete and marker removal/retry.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 70dae5c. A newly expired live reservation is now fenced and retained for a full retention window after the first delete. Only the follow-up cleanup pass removes that tombstone; a regression recreates the object after the first delete and verifies publication is rejected and the recreated body is removed.

@ask-bonk

ask-bonk Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Posted 1 actionable inline finding.

github run

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

Re-review exact head 70dae5c after retaining cleanup tombstones through a delayed second pass. Hard deadline: 8 minutes. Stop early enough to post actionable findings or an explicit no-findings verdict.

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