fix(plugin): stop workerd PG pool leak + defer /updates manifest for P999#2741
fix(plugin): stop workerd PG pool leak + defer /updates manifest for P999#2741riderx wants to merge 15 commits into
Conversation
Reuse one pg.Pool per Hyperdrive connection on workerd (closeClient was a no-op while every request allocated a new Pool). Defer /updates manifest json_agg until after the up-to-date short-circuit, background channel_self rate-limit writes, and add plugin_path_timing logs for P999 triage. Co-authored-by: Martin DONADIEU <martindonadieu@gmail.com>
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe plugin runtime now supports runtime-aware PostgreSQL clients, awaits client acquisition across handlers, hardens notification cleanup, defers manifest loading, bounds response-feature caching, and records timing data for slow update paths. ChangesPlugin runtime execution
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
Suggested labels: Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant updateWithPG
participant requestInfosPostgres
participant requestManifestEntriesPostgres
participant Logger
updateWithPG->>requestInfosPostgres: request information without manifest
requestInfosPostgres-->>updateWithPG: channel and override metadata
updateWithPG->>requestManifestEntriesPostgres: fetch manifest entries when required
requestManifestEntriesPostgres-->>updateWithPG: manifest entries
updateWithPG->>Logger: emit timing data for slow paths
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
Comment |
Merging this PR will not alter performance
Comparing Footnotes
|
…is 0 App-level manifest_bundle_count already gates the fetch; requiring version.manifest_count skipped real manifest rows when that column lagged. Co-authored-by: Martin DONADIEU <martindonadieu@gmail.com>
Backend/unit green; CF shard returned mass 429s with worker-port races. Co-authored-by: Martin DONADIEU <martindonadieu@gmail.com>
Local Miniflare sets CAPGO_PREVENT_BACKGROUND_FUNCTIONS and runs many concurrent requests on one isolate; a shared max:4 Pool hit the 10s connect timeout. Keep isolate pool reuse for production only. Co-authored-by: Martin DONADIEU <martindonadieu@gmail.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_f78edd21-56dd-4dd0-9276-716cfbb9a683) |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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/functions/_backend/plugin_runtime/utils/pg.ts`:
- Around line 368-369: Update getWorkerdPgPoolKey and its callers so the
shared-pool key includes the request-derived application_name alongside the
connection string and read-only options. Ensure pool lookup and creation use the
same complete key, preventing different application names from reusing one pool.
- Around line 1014-1016: Refactor the shouldFetchManifest assignment to remove
the nested ternary while preserving its current behavior: return false when
includeManifest is explicitly false, otherwise return true when
manifestBundleCount is null or undefined or greater than zero. Use a clear
boolean expression in place of the conditional nesting.
In `@supabase/functions/_backend/plugin_runtime/utils/update.ts`:
- Around line 460-466: Update the missing-bundle validation around the
version-specific lazy manifest fetch so that, after the query completes with no
manifest entries, a selected version lacking both r2_path and manifest rows
still follows the existing no_bundle error branch. Do not rely solely on the
positive app-level manifestBundleCount; preserve the existing no_bundle_url
behavior only for versions with an actual bundle URL or applicable
internal-version handling.
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 42d22f25-fcc6-4992-821b-29c40d38e443
📒 Files selected for processing (4)
supabase/functions/_backend/plugin_runtime/plugins/channel_self.tssupabase/functions/_backend/plugin_runtime/utils/pg.tssupabase/functions/_backend/plugin_runtime/utils/update.tstests/plugin-pg-pool-reuse.unit.test.ts
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Cap-go/capacitor-updater(manual)
There was a problem hiding this comment.
All reported issues were addressed across 4 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Use a stable shared-pool application_name/key, simplify shouldFetchManifest, restore no_bundle after empty deferred manifest fetch, and assert pools stay isolated per connection string. Co-authored-by: Martin DONADIEU <martindonadieu@gmail.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_6b16ca90-6d46-4ebc-a1e7-487e5ac682b0) |
…reuse Cloudflare Hyperdrive docs: create a new client each request; Hyperdrive owns the DB pool. Shared isolate Pool reuse was an unproven bet at plugin scale. Use Pool(max:1) per request (sync API) and always end via waitUntil. Co-authored-by: Martin DONADIEU <martindonadieu@gmail.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_63eab751-5c05-4661-ab08-08d51a5384b3) |
There was a problem hiding this comment.
Stale comment
Risk: medium. Not approving: Cursor Bugbot skipped (usage limit reached), so automated review did not complete. Human review is still needed for the plugin-runtime PG pool and /updates manifest changes; reviewers are already assigned.
Sent by Cursor Approval Agent: Pull Request Approver External
Co-authored-by: Martin DONADIEU <martindonadieu@gmail.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_7aaf0675-773c-4f96-8d03-745da751e79a) |
There was a problem hiding this comment.
Stale comment
Risk: medium. Not approving because Cursor Bugbot skipped (usage limit reached), so automated review did not complete, and this plugin hot-path change exceeds the low-risk approval threshold. Human review is still needed; reviewers are already assigned.
Sent by Cursor Approval Agent: Pull Request Approver External
Address review: close per-call Pools in sendEmailToOrgMembers, keep getPgClient inside try/finally for notification helpers, and assert Hyperdrive Client is not returned before connect() resolves. Co-authored-by: Martin DONADIEU <martindonadieu@gmail.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_f1533fe0-02a1-403c-b39c-0b0d2cbed280) |
There was a problem hiding this comment.
Stale comment
Risk: medium. Not approving because Cursor Bugbot did not complete (skipped due to usage limit), and this plugin hot-path change exceeds the low-risk approval threshold. Human review is still needed; reviewers are already assigned.
Sent by Cursor Approval Agent: Pull Request Approver External
Point getPgClient/closeClient comments at the Cloudflare page that explicitly says client.end() is not needed for Hyperdrive. Co-authored-by: Martin DONADIEU <martindonadieu@gmail.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_4de6699d-6965-48c1-9207-f91fc4545471) |
There was a problem hiding this comment.
Stale comment
Risk: medium. Not approving because Cursor Bugbot skipped (usage limit reached), so automated review did not complete, and this plugin hot-path change exceeds the low-risk approval threshold. Human review is still needed; reviewers are already assigned.
Sent by Cursor Approval Agent: Pull Request Approver External
There was a problem hiding this comment.
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/plugin_runtime/plugins/channel_self.ts (1)
636-652: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDeferred
record()correctly matches the P999 objective; drop the now-redundantas anycast.
pgClientis now properly typed asPluginPgClient, sogetDrizzleClient(pgClient as any, ...)no longer needs the cast — keeping it risks silently hiding future type mismatches.♻️ Proposed cleanup
- return await run(getDrizzleClient(pgClient as any, { logger: false })) + return await run(getDrizzleClient(pgClient, { logger: false }))🤖 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/plugin_runtime/plugins/channel_self.ts` around lines 636 - 652, Remove the redundant as any cast from the getDrizzleClient call in runChannelSelfWithPgClient, passing the properly typed pgClient directly while preserving the existing logger configuration and cleanup flow.supabase/functions/_backend/plugin_runtime/utils/pg.ts (2)
503-517: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
closeClientdoesn't guard against a synchronous throw fromdb.end().
Promise.resolve(db.end()).catch(...)only catches a rejected promise. Ifdb.end()throws synchronously (e.g., double-end()on some pg internal states), the throw escapes before.catch()is attached, andcloseClient— called from manyfinallyblocks — would itself throw and mask the original error/response. Wrap the call in an async IIFE so both sync and async failures are caught.🛡️ Proposed fix
export function closeClient(c: Context, db: PluginPgClient) { if (skipEndClients.has(db)) return - return backgroundTask(c, Promise.resolve(db.end()).catch((error: unknown) => { - cloudlogErr({ - requestId: c.get('requestId'), - message: 'PG client end failed', - error, - }) - })) + return backgroundTask(c, (async () => { + try { + await db.end() + } + catch (error: unknown) { + cloudlogErr({ + requestId: c.get('requestId'), + message: 'PG client end failed', + error, + }) + } + })()) }🤖 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/plugin_runtime/utils/pg.ts` around lines 503 - 517, Update closeClient so the db.end() invocation occurs inside an async IIFE or equivalent try/catch boundary, ensuring both synchronous throws and promise rejections reach the existing cloudlogErr handler. Preserve the skipEndClients early return, backgroundTask usage, request ID, and existing error message.
1559-1598: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick winEight admin analytics functions in
pg.tsstill leak their PostgreSQL client — the exact bug class this PR fixes elsewhere in the same file.Each of these functions now does
pgClient = await getPgClient(c, ...)but has nofinally { await closeClient(c, pgClient) }. On any runtime path that resolves to aPool(non-workerd Supabase Edge Functions, or workerd without a matching Hyperdrive binding for that DB URL), every call to these functions leaks a connection pool indefinitely. Sibling functions in this same file (getAdminOrganizationInsights,getAdminTrialPlanBreakdown,getLiveRegisteredUsersCount,getAdminPayingOrgBreakdown) were correctly fixed with thelet pgClient: PluginPgClient | undefined+finally { if (pgClient) await closeClient(c, pgClient) }pattern — apply the same pattern to all sites below.
supabase/functions/_backend/plugin_runtime/utils/pg.ts#L1559-L1598: wrapgetAdminDeploymentsTrend's body intry { ... } finally { if (pgClient) await closeClient(c, pgClient) }, hoistingpgClientto aletdeclared before thetry.supabase/functions/_backend/plugin_runtime/utils/pg.ts#L1697-L2003: apply the same fix togetAdminGlobalStatsTrend; note itscatchcurrently rethrows (throw e), so the client leaks on both success and failure.supabase/functions/_backend/plugin_runtime/utils/pg.ts#L2164-L2273: apply the same fix togetAdminEmailTypeBreakdown.supabase/functions/_backend/plugin_runtime/utils/pg.ts#L2284-L2356: apply the same fix togetAdminCustomerCountryBreakdown.supabase/functions/_backend/plugin_runtime/utils/pg.ts#L2849-L2937: apply the same fix togetAdminCancelledOrganizations.supabase/functions/_backend/plugin_runtime/utils/pg.ts#L2965-L3052: apply the same fix togetAdminTrialOrganizations.supabase/functions/_backend/plugin_runtime/utils/pg.ts#L3207-L3463: apply the same fix togetAdminOnboardingFunnel.supabase/functions/_backend/plugin_runtime/utils/pg.ts#L3465-L3546: apply the same fix togetAdminPluginBreakdown.🤖 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/plugin_runtime/utils/pg.ts` around lines 1559 - 1598, Admin analytics functions leak PostgreSQL clients because they do not close clients on every exit path. In supabase/functions/_backend/plugin_runtime/utils/pg.ts, update getAdminDeploymentsTrend (1559-1598), getAdminGlobalStatsTrend (1697-2003), getAdminEmailTypeBreakdown (2164-2273), getAdminCustomerCountryBreakdown (2284-2356), getAdminCancelledOrganizations (2849-2937), getAdminTrialOrganizations (2965-3052), getAdminOnboardingFunnel (3207-3463), and getAdminPluginBreakdown (3465-3546) by hoisting pgClient as let pgClient: PluginPgClient | undefined before each try, assigning it inside, and adding finally cleanup with closeClient when defined; preserve existing catch behavior, including rethrowing in getAdminGlobalStatsTrend.
🤖 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.
Outside diff comments:
In `@supabase/functions/_backend/plugin_runtime/plugins/channel_self.ts`:
- Around line 636-652: Remove the redundant as any cast from the
getDrizzleClient call in runChannelSelfWithPgClient, passing the properly typed
pgClient directly while preserving the existing logger configuration and cleanup
flow.
In `@supabase/functions/_backend/plugin_runtime/utils/pg.ts`:
- Around line 503-517: Update closeClient so the db.end() invocation occurs
inside an async IIFE or equivalent try/catch boundary, ensuring both synchronous
throws and promise rejections reach the existing cloudlogErr handler. Preserve
the skipEndClients early return, backgroundTask usage, request ID, and existing
error message.
- Around line 1559-1598: Admin analytics functions leak PostgreSQL clients
because they do not close clients on every exit path. In
supabase/functions/_backend/plugin_runtime/utils/pg.ts, update
getAdminDeploymentsTrend (1559-1598), getAdminGlobalStatsTrend (1697-2003),
getAdminEmailTypeBreakdown (2164-2273), getAdminCustomerCountryBreakdown
(2284-2356), getAdminCancelledOrganizations (2849-2937),
getAdminTrialOrganizations (2965-3052), getAdminOnboardingFunnel (3207-3463),
and getAdminPluginBreakdown (3465-3546) by hoisting pgClient as let pgClient:
PluginPgClient | undefined before each try, assigning it inside, and adding
finally cleanup with closeClient when defined; preserve existing catch behavior,
including rethrowing in getAdminGlobalStatsTrend.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: b1fb8bba-0ae7-47b1-8ed5-13cd6aa31d16
📒 Files selected for processing (10)
supabase/functions/_backend/plugin_runtime/plugins/channel_self.tssupabase/functions/_backend/plugin_runtime/plugins/stats.tssupabase/functions/_backend/plugin_runtime/private/latency.tssupabase/functions/_backend/plugin_runtime/utils/manifest_size.tssupabase/functions/_backend/plugin_runtime/utils/notifications.tssupabase/functions/_backend/plugin_runtime/utils/org_email_notifications.tssupabase/functions/_backend/plugin_runtime/utils/pg.tssupabase/functions/_backend/plugin_runtime/utils/update.tstests/plans-onboarding-reminder.unit.test.tstests/plugin-pg-client-lifecycle.unit.test.ts
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Cap-go/capacitor-updater(manual)
Protect the path that returns files: start the indexed manifest.app_version_id query as soon as we know the device is not up-to-date, and await it in parallel with signed URL generation. Keep channel-query json_agg off so up-to-date stays cheap. Co-authored-by: Martin DONADIEU <martindonadieu@gmail.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_3f548738-2db0-421f-bd90-85c5d50a7465) |
There was a problem hiding this comment.
Stale comment
Risk: medium. Not approving because Cursor Bugbot skipped (usage limit reached), so automated review did not complete, and this plugin hot-path change exceeds the low-risk approval threshold. Human review is still needed; reviewers are already assigned.
Sent by Cursor Approval Agent: Pull Request Approver External
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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/functions/_backend/plugin_runtime/utils/update.ts`:
- Around line 502-514: Update deferredManifestPromise in the
needsDeferredManifest branch to call execute() on the lazy
requestManifestEntriesPostgres query, creating one native promise. Reuse that
promise for the existing early catch and later Promise.all flow so the manifest
lookup executes only once.
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 78790589-5cfb-4950-a44c-d02a55df522b
📒 Files selected for processing (1)
supabase/functions/_backend/plugin_runtime/utils/update.ts
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Cap-go/capacitor-updater(manual)
| // New-version path is the latency-critical one. Start the indexed manifest | ||
| // lookup immediately so it overlaps auto-update gates + signed URL work. | ||
| // Still NOT using channel-query json_agg (that was the P999 tax on up-to-date). | ||
| // Index: idx_manifest_app_version_id. | ||
| const needsDeferredManifest = !version.external_url | ||
| && fetchManifestEntries | ||
| && (!manifestEntries || manifestEntries.length === 0) | ||
| const startManifestFetch = needsDeferredManifest ? performance.now() : 0 | ||
| const deferredManifestPromise = needsDeferredManifest | ||
| ? requestManifestEntriesPostgres(c, version.id, drizzleClient) | ||
| : null | ||
| // If a gate returns early, this in-flight query must not become an unhandled rejection. | ||
| deferredManifestPromise?.catch(() => {}) |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win
Reuse a native promise for the deferred manifest query.
requestManifestEntriesPostgres(...) returns a lazy Drizzle query object, so the early .catch(() => {}) and the later Promise.all([...]) each re-run the query. That doubles the manifest lookup on the hot path and defeats the overlap optimization. Call .execute() when creating deferredManifestPromise, then reuse that promise for both places.
🤖 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/plugin_runtime/utils/update.ts` around lines 502
- 514, Update deferredManifestPromise in the needsDeferredManifest branch to
call execute() on the lazy requestManifestEntriesPostgres query, creating one
native promise. Reuse that promise for the existing early catch and later
Promise.all flow so the manifest lookup executes only once.
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Confidence score: 3/5
- In
supabase/functions/_backend/plugin_runtime/utils/update.ts, the Drizzle query builder is being consumed via.catch(() => {})and then awaited later, which can execute the same SQL twice because the builder is thenable; that creates real regression risk like duplicate updates/inserts and harder-to-trace behavior from swallowed errors — execute the query exactly once (store/await once) and handle errors on that single execution path.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="supabase/functions/_backend/plugin_runtime/utils/update.ts">
<violation number="1" location="supabase/functions/_backend/plugin_runtime/utils/update.ts:514">
P1: Drizzle query builders are thenables — they re-execute the underlying SQL each time `.then()`, `.catch()`, or `await` is invoked on them. Here, `.catch(() => {})` on line 514 triggers the first execution, and the later `Promise.all([..., deferredManifestPromise])` triggers a second execution because it's still the raw thenable, not a settled native Promise.
To execute the query exactly once and reuse the result, convert to a native promise eagerly:
```ts
const deferredManifestPromise = needsDeferredManifest
? Promise.resolve(requestManifestEntriesPostgres(c, version.id, drizzleClient))
: null
Wrapping with Promise.resolve(thenable) subscribes once and returns a native Promise that can be safely .catch()-ed and later awaited in Promise.all without re-running the query.
</details>
<sub>**Tip**: Review your code locally with the [cubic CLI](https://docs.cubic.dev/ide/cli-review?utm_source=github&utm_content=general_review_body) to iterate faster.<br /><br />[Re-trigger cubic](https://www.cubic.dev/action/re-review/pr/Cap-go/capgo.app/2741/ai_pr_review_1784923562221_b3939997-282c-45a4-aede-4b7177fc32f8?returnTo=https%3A%2F%2Fgithub.com%2FCap-go%2Fcapgo.app%2Fpull%2F2741)</sub>
<!-- cubic:review-post:ai_pr_review_1784923562221_b3939997-282c-45a4-aede-4b7177fc32f8:a84680749c7b3262df586b559db22a3edc362733:0cb8ba32-7f6d-46fc-bbe8-0aa4ee97e059 -->
| ? requestManifestEntriesPostgres(c, version.id, drizzleClient) | ||
| : null | ||
| // If a gate returns early, this in-flight query must not become an unhandled rejection. | ||
| deferredManifestPromise?.catch(() => {}) |
There was a problem hiding this comment.
P1: Drizzle query builders are thenables — they re-execute the underlying SQL each time .then(), .catch(), or await is invoked on them. Here, .catch(() => {}) on line 514 triggers the first execution, and the later Promise.all([..., deferredManifestPromise]) triggers a second execution because it's still the raw thenable, not a settled native Promise.
To execute the query exactly once and reuse the result, convert to a native promise eagerly:
const deferredManifestPromise = needsDeferredManifest
? Promise.resolve(requestManifestEntriesPostgres(c, version.id, drizzleClient))
: nullWrapping with Promise.resolve(thenable) subscribes once and returns a native Promise that can be safely .catch()-ed and later awaited in Promise.all without re-running the query.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At supabase/functions/_backend/plugin_runtime/utils/update.ts, line 514:
<comment>Drizzle query builders are thenables — they re-execute the underlying SQL each time `.then()`, `.catch()`, or `await` is invoked on them. Here, `.catch(() => {})` on line 514 triggers the first execution, and the later `Promise.all([..., deferredManifestPromise])` triggers a second execution because it's still the raw thenable, not a settled native Promise.
To execute the query exactly once and reuse the result, convert to a native promise eagerly:
```ts
const deferredManifestPromise = needsDeferredManifest
? Promise.resolve(requestManifestEntriesPostgres(c, version.id, drizzleClient))
: null
Wrapping with Promise.resolve(thenable) subscribes once and returns a native Promise that can be safely .catch()-ed and later awaited in Promise.all without re-running the query.
Add a local Postgres A/B load test for the /updates manifest path. At 5k files/version (205k rows, conc=50): new_version p95 improved 34% and up_to-date p95 improved 96%. Gate fails CI-style if new_version regresses. Commit results + summary for PR evidence. Co-authored-by: Martin DONADIEU <martindonadieu@gmail.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_7f6538fc-6d7a-492f-9746-e1e24b5c640c) |
There was a problem hiding this comment.
Risk: medium. Not approving because Cursor Bugbot skipped (usage limit reached), so automated review did not complete, and this plugin hot-path change exceeds the low-risk approval threshold. Human review is still needed; reviewers are already assigned.
Sent by Cursor Approval Agent: Pull Request Approver External
There was a problem hiding this comment.
1 issue found across 5 files (changes from recent commits).
Confidence score: 2/5
- In
scripts/bench_updates_manifest_path.ts, the benchmark setup can use the appDATABASE_URLand drop thepublicschema, which creates a concrete data-loss risk if someone runs it in a non-isolated environment; switch to a dedicated benchmark/replica-only connection variable and add an explicit guard that refuses known app/staging/production URLs before any destructive step.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="scripts/bench_updates_manifest_path.ts">
<violation number="1" location="scripts/bench_updates_manifest_path.ts:330">
P0: Running this script in an environment with the application `DATABASE_URL` destroys that database's `public` schema before benchmarking. Use a dedicated benchmark/replica-only connection variable plus a guard that rejects non-benchmark targets, rather than accepting the general runtime DB URL.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| } | ||
|
|
||
| async function main() { | ||
| const databaseUrl = process.env.DATABASE_URL || DEFAULT_URL |
There was a problem hiding this comment.
P0: Running this script in an environment with the application DATABASE_URL destroys that database's public schema before benchmarking. Use a dedicated benchmark/replica-only connection variable plus a guard that rejects non-benchmark targets, rather than accepting the general runtime DB URL.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/bench_updates_manifest_path.ts, line 330:
<comment>Running this script in an environment with the application `DATABASE_URL` destroys that database's `public` schema before benchmarking. Use a dedicated benchmark/replica-only connection variable plus a guard that rejects non-benchmark targets, rather than accepting the general runtime DB URL.</comment>
<file context>
@@ -0,0 +1,506 @@
+}
+
+async function main() {
+ const databaseUrl = process.env.DATABASE_URL || DEFAULT_URL
+ const files = Number(argValue('--files', '5000'))
+ const concurrency = Number(argValue('--concurrency', '50'))
</file context>
|





Summary (AI generated)
pg.Clientper request,await connect(), noend()(connection lifecycle).json_agg. New-version path uses indexedmanifest WHERE app_version_id = ?, started right after up-to-date check and overlapped with signed URL work.bun run bench:updates-manifest-path): at 5k files/version, 205k rows, conc=50, new_version p95 −34% vs old json_agg; up_to_date p95 −96%. Seescripts/bench/updates_manifest_path_summary.md.Motivation (AI generated)
Memory sawtooth + stuck P999. Deferring manifest must not punish devices that get a bundle. Guessing is not allowed — measured under concurrent load against a selective
idx_manifest_app_version_id.Business Impact (AI generated)
Keeps delivery path healthy while making the common up-to-date check ~20× cheaper on the DB critical path.
Test Plan (AI generated)
bunx vitest run tests/plugin-pg-client-lifecycle.unit.test.tsbun run typecheck:backendbun run bench:updates-manifest-path(PASS gate: new_version not destroyed)plugin_path_timingoutcome=new_versionvs baselineLoad test evidence (AI generated)
EXPLAIN: deferred path uses Index Scan on
idx_manifest_app_version_id.Generated with AI
Summary by CodeRabbit