Skip to content

fix(cost,db): bound runaway AI spend, and stop reporting zero for a metric that has been broken since it shipped - #9146

Merged
JSONbored merged 1 commit into
mainfrom
fix/9060-9061-9084-cost
Jul 27, 2026
Merged

fix(cost,db): bound runaway AI spend, and stop reporting zero for a metric that has been broken since it shipped#9146
JSONbored merged 1 commit into
mainfrom
fix/9060-9061-9084-cost

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

#9060 — a failed AI review cached nothing, so the whole expensive prologue re-ran every two minutes, forever

Both failure exits returned undefined, and the caller only writes a cache row for a defined result. So a pass that threw inside the try (a DB hiccup, a GitHub 5xx during grounding, an enrichment timeout) or came back non-ok recorded nothing — and the next tick missed the cache and re-executed everything: listPullRequestFiles, up to 96k characters of file content fetched for grounding, RAG embeddings, impact-map embeddings, culture profile, the external enrichment POST, then the model call. That is the shape and cadence of the known 259-calls-in-24h incident; the #regate-churn fix bounded re-spend for disputed verdicts and never covered failures.

Failures now return a result that is cacheable: false, persistable: true, and the pair means something specific here:

  • not cacheable — this is not a verdict and must never be served as one, so the PR is reviewed properly on the next attempt;
  • persistable — but the row is written, so the non-cacheable retry cooldown applies and the prologue runs once per cooldown window instead of once per tick.

That is the exact inverse of aiReviewLockContendedResult, which is persistable: false precisely because a concurrent pass writes the real result within seconds. Here nothing else is coming, which is why the cooldown must.

disabled and unavailable are deliberately excluded — the operator switched AI review off, or no provider is bound. Those are configuration states, not failed attempts, and must keep returning undefined rather than holding PRs for a review nobody configured.

Two compounding parts, both named in the issue:

  • The budget ceiling sat after the spend it bounds. It needs this call's estimated cost, which needs the assembled prompt — so on an exhausted budget every tick paid for the entire prologue and then declined to make the one call the prologue existed to support. A cheap "is the budget already spent" pre-check now runs first; it needs no prompt.
  • Embeddings were booked at estimatedNeurons: 0. So RAG and impact-map spend never moved the governor's counter — meaning the ceiling could never converge and the loop never self-limited, no matter how many times it ran. They are now charged a real estimate with a floor of 1: a round trip is never free, and "free" is exactly the accounting that let this hide.

#9061 — no per-repo AI ceiling on the path we actually run

A per-repo daily limit existed only for BYOK. On the self-host, where reviews run on the free/default chain, one runaway repo could consume the entire instance-wide allowance with nothing stopping it. Added as AI_DAILY_REPO_CALL_LIMIT, mirroring the BYOK ceiling's shape — and the two counters now share one implementation, since the byok:% model filter was their only difference and was the whole gap.

Also: with AI_EMBED unset, embeddings route onto the frontier review chain and bill at frontier rates. The fallback stays (removing it would silently disable RAG for every existing deployment relying on it) but it now warns, so an operator knows what they are paying for.

#9084 — the review-effort metric has been silently zero on Postgres

Verified live: avg((metadata_json::jsonb ->> 'reviewEffortMinutes'))ERROR: function avg(text) does not exist. json_extract translates to ->>, which yields text, so the enclosing AVG resolved to avg(text). Both call sites swallow the error, so a published number reported nothing and nobody was told. Fixed with an explicit numeric cast valid in both dialects, plus NULLIF for the empty string Postgres would otherwise reject outright.

Same family, same file. target_key is not uniformly two-segment — regateRepairTargetKey mints repo#pr#headSha. On SQLite the INTEGER cast of pr#sha is lenient garbage; on Postgres it aborts the whole query, so one three-segment row among the filtered event types took the entire public-stats read to [] and the homepage counters silently to zero. Those keys are now excluded before the cast, counted with length()/replace() rather than a nested instr() because those need no dialect translation at all.

Which matters, because the nested form would not have worked — writing the guard surfaced a latent bug in the instr translation itself. The regex stopped its haystack at the first comma, so a nested instr(substr(a, instr(a,'#')+1), '#') left the outer call untranslated, producing SQL that fails on Postgres with the exact "function instr does not exist" that rule exists to prevent, into a fail-safe read that swallows it. Replaced with a paren-balanced scan that skips quoted literals and recurses, so nesting depth is unbounded and a comma inside a string can never be mistaken for structure. Nothing nests instr today; this makes sure the first thing that does is not silently broken on the self-host.

Policy reversal — please read

Three tests asserted that a failed AI review yields nothing — no result, no finding. That is fail-safe in the sense of "never fabricates a verdict", and it was also the bug: returning undefined is precisely what stopped the failure from ever being recorded, which is what let the loop re-run forever.

The result returned now is still not a verdict — cacheable: false, empty notes. Notes are empty deliberately: the downstream "required AI review produced no public summary" audit keys on exactly that emptiness, so a human-readable apology there would read as a real assessment and silently suppress the audit plus Sentry signal an operator needs. The hold reaches the contributor through the finding instead.

Verification

  • npm run test:ci — exit 0, full gate green.
  • 100% line and branch coverage on all 290 added src/ lines, verified by intersecting git diff -U0 against lcov.info DA:/BRDA: records.
  • New env var means selfhost:env-reference was regenerated and committed.

Closes #9060
Closes #9061
Closes #9084

…etric that has been broken since it shipped

#9060 — a failed AI review cached nothing, so the whole expensive prologue re-ran
every two minutes, forever.
Both failure exits returned `undefined`, and the caller only writes a cache row
for a DEFINED result. So a pass that threw inside the try (a DB hiccup, a GitHub
5xx during grounding, an enrichment timeout) or came back non-ok recorded nothing
— and the next tick missed the cache and re-executed everything: list files, up to
96k characters of file content fetched for grounding, RAG embeddings, impact-map
embeddings, culture profile, the external enrichment POST, then the model call.
That is the shape and cadence of the known 259-calls-in-24h incident; the
#regate-churn fix bounded re-spend for DISPUTED verdicts and never covered
failures.

Failures now return a result that is `cacheable: false, persistable: true`, and
the pair means something specific: not a verdict, so the PR is reviewed properly
next time — but the row is WRITTEN, so the retry cooldown bounds the prologue.
That is the exact inverse of the lock-contention placeholder, which is
persistable:false precisely because a concurrent pass writes the real result
within seconds. Here nothing else is coming, which is why the cooldown must.

`disabled` and `unavailable` are deliberately excluded: the operator switched AI
review off, or no provider is bound. Those are configuration states, not failed
attempts, and must keep returning undefined rather than holding PRs for a review
nobody configured.

Two compounding parts. The budget ceiling sat AFTER the spend it bounds — it needs
this call's estimated cost, which needs the assembled prompt — so on an exhausted
budget every tick paid for the entire prologue and then declined to make the one
call the prologue existed to support. A cheap "is the budget already spent"
pre-check now runs before any of it. And every embedding was booked at
`estimatedNeurons: 0`, so RAG and impact-map spend never moved the governor's
counter: the ceiling could not converge and the loop never self-limited. Embeddings
are now charged a real estimate, with a floor of 1 — a round trip is never free,
and free is exactly the accounting that let this hide.

#9061 — no per-repo AI ceiling on the path we actually run.
A per-repo daily limit existed only for BYOK. On the self-host, where reviews run
on the free/default chain, one runaway repo could consume the entire instance-wide
allowance with nothing stopping it. Added as AI_DAILY_REPO_CALL_LIMIT, mirroring
the BYOK ceiling's shape; the two counters now share one implementation, since the
`byok:%` model filter was their only difference and was the whole gap. Also: with
AI_EMBED unset, embeddings route onto the frontier review chain and bill at
frontier rates. The fallback stays (removing it would silently disable RAG for
existing deployments) but it now warns, so an operator knows.

#9084 — the review-effort metric has been silently zero on Postgres.
Verified live: `avg((metadata_json::jsonb ->> 'reviewEffortMinutes'))` →
"function avg(text) does not exist". json_extract translates to `->>`, which
yields text, so the enclosing AVG resolved to avg(text). Both call sites swallow
the error, so a published number reported nothing and nobody was told. Fixed with
an explicit numeric cast that is valid in both dialects, plus NULLIF for the empty
string Postgres would otherwise reject.

Same family, same file: target_key is not uniformly two-segment —
regateRepairTargetKey mints `repo#pr#headSha`. On SQLite the INTEGER cast of
`pr#sha` is lenient garbage; on Postgres it aborts the WHOLE query, so ONE
three-segment row among the filtered event types took the entire public-stats read
to [] and the homepage counters silently to zero. Those keys are now excluded
before the cast, counted with length()/replace() rather than a nested instr()
because those need no dialect translation at all.

Which matters, because the nested form would not have worked: writing the guard
surfaced a latent bug in the instr translation itself. The regex stopped its
haystack at the first comma, so a nested `instr(substr(a, instr(a,'#')+1), '#')`
left the OUTER call untranslated — producing SQL that fails on Postgres with the
exact "function instr does not exist" the rule exists to prevent, into a fail-safe
read that swallows it. Replaced with a paren-balanced scan that skips quoted
literals and recurses, so nesting depth is unbounded and a comma inside a string
can never be mistaken for structure.

POLICY REVERSAL, called out explicitly. Three tests asserted that a failed AI
review yields nothing — no result, no finding. That is fail-safe in the sense of
"never fabricates a verdict", and it was also the bug: returning undefined is
precisely what stopped the failure from ever being recorded. The result returned
now is still not a verdict (cacheable:false, empty notes). Notes are empty
deliberately: the downstream "required AI review produced no public summary" audit
keys on that emptiness, and a human-readable apology there would read as a real
assessment and silently suppress the operator signal.

Local gate green end to end (npm run test:ci, exit 0). 100% line and branch
coverage on all 290 added src lines.

Closes #9060
Closes #9061
Closes #9084
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@github-actions

Copy link
Copy Markdown
Contributor

Logic backtest

Replayed 0 historical case(s) for linked_issue_scope_mismatch through the base (0746e6c) and head (1108e82) versions of its detection logic (corpus checksum 4f53cda18c2b).

Backtest comparison: linked_issue_scope_mismatch

Verdict: unchanged — no comparable axis moved.

Advisory only — this check never blocks merge (#8105).

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
loopover-ui 1108e82 Commit Preview URL

Branch Preview URL
Jul 27 2026, 04:15 AM

@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

Bundle Report

Changes will increase total bundle size by 61 bytes (0.0%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
loopover-ui 7.42MB 61 bytes (0.0%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: loopover-ui

Assets Changed:

Asset Name Size Change Total Size Change (%)
assets/add-scalar-classes-DeJbEOwJ.js (New) 2.17MB 2.17MB 100.0% 🚀
assets/tanstack-vendor-B4ZO9PHT.js (New) 803.04kB 803.04kB 100.0% 🚀
assets/docs.fumadocs-spike-api-reference-DcCvObhL.js (New) 442.88kB 442.88kB 100.0% 🚀
assets/AgentScalarChatInterface.vue-uHDPW65F.js (New) 201.71kB 201.71kB 100.0% 🚀
assets/modal-5-NrVVLW.js (New) 184.39kB 184.39kB 100.0% 🚀
assets/client-_bZEbloi.js (New) 146.06kB 146.06kB 100.0% 🚀
assets/self-hosting-configuration-Clri3J5L.js (New) 101.39kB 101.39kB 100.0% 🚀
assets/maintainer-panel-CJVQjxPs.js (New) 79.0kB 79.0kB 100.0% 🚀
assets/routes-C47JiqCj.js (New) 35.77kB 35.77kB 100.0% 🚀
assets/owner-panel-6Ftbmyvn.js (New) 27.52kB 27.52kB 100.0% 🚀
assets/app-BUdDr_Hf.js (New) 25.78kB 25.78kB 100.0% 🚀
assets/ui-vendor-D2xF876d.js (New) 22.28kB 22.28kB 100.0% 🚀
assets/miner-panel-GXnGYtZN.js (New) 20.24kB 20.24kB 100.0% 🚀
assets/app.runs-Jo0oAY0O.js (New) 20.22kB 20.22kB 100.0% 🚀
assets/api._op-DeNOpRQh.js (New) 17.57kB 17.57kB 100.0% 🚀
assets/self-hosting-docs-audit-BtnlW0V6.js (New) 16.6kB 16.6kB 100.0% 🚀
assets/docs._slug-qutJjiaX.js (New) 15.37kB 15.37kB 100.0% 🚀
assets/playground-panel-aIz7NKUH.js (New) 14.43kB 14.43kB 100.0% 🚀
assets/fairness-C3bjhAXb.js (New) 10.6kB 10.6kB 100.0% 🚀
assets/app.audit-BDjATJWH.js (New) 10.08kB 10.08kB 100.0% 🚀
assets/app.config-generator-BQ1-n0o7.js (New) 10.06kB 10.06kB 100.0% 🚀
assets/maintainers-Blc3td7Z.js (New) 8.06kB 8.06kB 100.0% 🚀
assets/miners-DWxdTEly.js (New) 7.91kB 7.91kB 100.0% 🚀
assets/agents-44qqnHdb.js (New) 7.74kB 7.74kB 100.0% 🚀
assets/commands-panel-Beinj28u.js (New) 6.65kB 6.65kB 100.0% 🚀
assets/maintainer-workflow-nTasPKP5.js (New) 6.52kB 6.52kB 100.0% 🚀
assets/digest-panel-GqOLdnLa.js (New) 6.15kB 6.15kB 100.0% 🚀
assets/repos._owner._repo.quality-Bi4dDp2v.js (New) 6.14kB 6.14kB 100.0% 🚀
assets/docs-nav-CMsnTZOB.js (New) 5.95kB 5.95kB 100.0% 🚀
assets/docs.index-rOsiynp3.js (New) 5.95kB 5.95kB 100.0% 🚀
assets/api.index-CCQCicHH.js (New) 4.7kB 4.7kB 100.0% 🚀
assets/docs-SRrv-kYR.js (New) 2.7kB 2.7kB 100.0% 🚀
assets/api-D0Q-tQYM.js (New) 2.69kB 2.69kB 100.0% 🚀
assets/docs-page-9WCvcPVS.js (New) 2.1kB 2.1kB 100.0% 🚀
assets/table-BUU_BHB4.js (New) 1.75kB 1.75kB 100.0% 🚀
assets/app.workbench-CYrOzsXd.js (New) 1.58kB 1.58kB 100.0% 🚀
assets/tabs-sVTAzfrq.js (New) 1.39kB 1.39kB 100.0% 🚀
assets/app.repos-DZNg7vap.js (New) 1.07kB 1.07kB 100.0% 🚀
assets/input-UksDPVEw.js (New) 796 bytes 796 bytes 100.0% 🚀
assets/file-cog-DcRoEUJp.js (New) 758 bytes 758 bytes 100.0% 🚀
assets/app.maintainer-BJkB6YI6.js (New) 502 bytes 502 bytes 100.0% 🚀
assets/app.owner-D83LLNqq.js (New) 474 bytes 474 bytes 100.0% 🚀
assets/app.commands-BpbLZ_0a.js (New) 455 bytes 455 bytes 100.0% 🚀
assets/app.playground-CS405Gta.js (New) 442 bytes 442 bytes 100.0% 🚀
assets/index-u1KNH_4l.js (New) 438 bytes 438 bytes 100.0% 🚀
assets/app.digest-BNZ9SuT-.js (New) 430 bytes 430 bytes 100.0% 🚀
assets/eye-off-D1rX1Mew.js (New) 430 bytes 430 bytes 100.0% 🚀
assets/app.miner-hS5z6pDy.js (New) 422 bytes 422 bytes 100.0% 🚀
assets/key-round-DLJaETYE.js (New) 355 bytes 355 bytes 100.0% 🚀
assets/bot-D2o2bCF9.js (New) 328 bytes 328 bytes 100.0% 🚀
assets/trash-2-BqEBEFUV.js (New) 328 bytes 328 bytes 100.0% 🚀
assets/save-CvAigVqp.js (New) 327 bytes 327 bytes 100.0% 🚀
assets/git-pull-request-arrow-BA8WxGPd.js (New) 321 bytes 321 bytes 100.0% 🚀
assets/list-checks-BHh5fLpu.js (New) 279 bytes 279 bytes 100.0% 🚀
assets/compass-DCUAwm_m.js (New) 251 bytes 251 bytes 100.0% 🚀
assets/history-L7cu4_Du.js (New) 237 bytes 237 bytes 100.0% 🚀
assets/message-square-DGVekwJN.js (New) 233 bytes 233 bytes 100.0% 🚀
assets/lock-CNUHKdhV.js (New) 206 bytes 206 bytes 100.0% 🚀
assets/rotate-cw-DFXF2jw1.js (New) 201 bytes 201 bytes 100.0% 🚀
assets/play-R3t2eVq4.js (New) 190 bytes 190 bytes 100.0% 🚀
assets/circle-check-CnLXYsCn.js (New) 178 bytes 178 bytes 100.0% 🚀
assets/search-i6D7Wmiw.js (New) 174 bytes 174 bytes 100.0% 🚀
assets/add-scalar-classes-Ce74kggI.js (Deleted) -2.17MB 0 bytes -100.0% 🗑️
assets/tanstack-vendor-DC0qHFe6.js (Deleted) -803.04kB 0 bytes -100.0% 🗑️
assets/docs.fumadocs-spike-api-reference-C73S6bBM.js (Deleted) -442.88kB 0 bytes -100.0% 🗑️
assets/AgentScalarChatInterface.vue-DaEHwWgv.js (Deleted) -201.71kB 0 bytes -100.0% 🗑️
assets/modal-ChnIPEmW.js (Deleted) -184.39kB 0 bytes -100.0% 🗑️
assets/client-BVoQh7bI.js (Deleted) -146.06kB 0 bytes -100.0% 🗑️
assets/self-hosting-configuration-D4EI4E05.js (Deleted) -101.33kB 0 bytes -100.0% 🗑️
assets/maintainer-panel-Ck0sPYZJ.js (Deleted) -79.0kB 0 bytes -100.0% 🗑️
assets/routes-CErOGDj7.js (Deleted) -35.77kB 0 bytes -100.0% 🗑️
assets/owner-panel-DfV697zP.js (Deleted) -27.52kB 0 bytes -100.0% 🗑️
assets/app-BSBsHJgp.js (Deleted) -25.78kB 0 bytes -100.0% 🗑️
assets/ui-vendor-DDQqun4G.js (Deleted) -22.28kB 0 bytes -100.0% 🗑️
assets/miner-panel-D2sMElVv.js (Deleted) -20.24kB 0 bytes -100.0% 🗑️
assets/app.runs-BAkjMJwG.js (Deleted) -20.22kB 0 bytes -100.0% 🗑️
assets/api._op-C7PMBK_G.js (Deleted) -17.57kB 0 bytes -100.0% 🗑️
assets/self-hosting-docs-audit-BS79n9FC.js (Deleted) -16.6kB 0 bytes -100.0% 🗑️
assets/docs._slug-aATfjF_W.js (Deleted) -15.37kB 0 bytes -100.0% 🗑️
assets/playground-panel-CrVf9XFf.js (Deleted) -14.43kB 0 bytes -100.0% 🗑️
assets/fairness-DmVFOtJX.js (Deleted) -10.6kB 0 bytes -100.0% 🗑️
assets/app.audit-BZuly2jN.js (Deleted) -10.08kB 0 bytes -100.0% 🗑️
assets/app.config-generator-C2YmjH-K.js (Deleted) -10.06kB 0 bytes -100.0% 🗑️
assets/maintainers-BCMmorvL.js (Deleted) -8.06kB 0 bytes -100.0% 🗑️
assets/miners-BYbPOwwN.js (Deleted) -7.91kB 0 bytes -100.0% 🗑️
assets/agents-SUZCH_TF.js (Deleted) -7.74kB 0 bytes -100.0% 🗑️
assets/commands-panel-niP3QMU5.js (Deleted) -6.65kB 0 bytes -100.0% 🗑️
assets/maintainer-workflow-DRtppeZR.js (Deleted) -6.52kB 0 bytes -100.0% 🗑️
assets/digest-panel-DBSXESrY.js (Deleted) -6.15kB 0 bytes -100.0% 🗑️
assets/repos._owner._repo.quality-DOK4KGwm.js (Deleted) -6.14kB 0 bytes -100.0% 🗑️
assets/docs-nav-BurAwgVt.js (Deleted) -5.95kB 0 bytes -100.0% 🗑️
assets/docs.index-DAxLreZ_.js (Deleted) -5.95kB 0 bytes -100.0% 🗑️
assets/api.index-By4xS1T7.js (Deleted) -4.7kB 0 bytes -100.0% 🗑️
assets/docs-D4uAS-pF.js (Deleted) -2.7kB 0 bytes -100.0% 🗑️
assets/api-Cox05gC1.js (Deleted) -2.69kB 0 bytes -100.0% 🗑️
assets/docs-page-Cqrz_bTM.js (Deleted) -2.1kB 0 bytes -100.0% 🗑️
assets/table-CmGzHOFV.js (Deleted) -1.75kB 0 bytes -100.0% 🗑️
assets/app.workbench-D4dxRUJ7.js (Deleted) -1.58kB 0 bytes -100.0% 🗑️
assets/tabs-DBtBGmja.js (Deleted) -1.39kB 0 bytes -100.0% 🗑️
assets/app.repos-CbgxrOm-.js (Deleted) -1.07kB 0 bytes -100.0% 🗑️
assets/input-CGfQqjlT.js (Deleted) -796 bytes 0 bytes -100.0% 🗑️
assets/file-cog-B7Y_H7hL.js (Deleted) -758 bytes 0 bytes -100.0% 🗑️
assets/app.maintainer-BnDI_zBK.js (Deleted) -502 bytes 0 bytes -100.0% 🗑️
assets/app.owner-VxoMxKz0.js (Deleted) -474 bytes 0 bytes -100.0% 🗑️
assets/app.commands-DJJp5c7x.js (Deleted) -455 bytes 0 bytes -100.0% 🗑️
assets/app.playground-DkNxr8ke.js (Deleted) -442 bytes 0 bytes -100.0% 🗑️
assets/index-C9SG9x4i.js (Deleted) -438 bytes 0 bytes -100.0% 🗑️
assets/app.digest-BIKwcElC.js (Deleted) -430 bytes 0 bytes -100.0% 🗑️
assets/eye-off-Bh1uW5IB.js (Deleted) -430 bytes 0 bytes -100.0% 🗑️
assets/app.miner-iOCmwK0r.js (Deleted) -422 bytes 0 bytes -100.0% 🗑️
assets/key-round-CQzmy1VQ.js (Deleted) -355 bytes 0 bytes -100.0% 🗑️
assets/bot-O8WudBzN.js (Deleted) -328 bytes 0 bytes -100.0% 🗑️
assets/trash-2-BKUGqTAN.js (Deleted) -328 bytes 0 bytes -100.0% 🗑️
assets/save-D82Kyrel.js (Deleted) -327 bytes 0 bytes -100.0% 🗑️
assets/git-pull-request-arrow-BprCPKIU.js (Deleted) -321 bytes 0 bytes -100.0% 🗑️
assets/list-checks-BQeQYIcm.js (Deleted) -279 bytes 0 bytes -100.0% 🗑️
assets/compass-DB1TenMT.js (Deleted) -251 bytes 0 bytes -100.0% 🗑️
assets/history-BKdRqYEe.js (Deleted) -237 bytes 0 bytes -100.0% 🗑️
assets/message-square-CSXSXHxz.js (Deleted) -233 bytes 0 bytes -100.0% 🗑️
assets/lock-GV6z_D_Y.js (Deleted) -206 bytes 0 bytes -100.0% 🗑️
assets/rotate-cw-BL_P9U-s.js (Deleted) -201 bytes 0 bytes -100.0% 🗑️
assets/play-Cn4BJJSG.js (Deleted) -190 bytes 0 bytes -100.0% 🗑️
assets/circle-check-C5JehnHY.js (Deleted) -178 bytes 0 bytes -100.0% 🗑️
assets/search-CcVeJDb2.js (Deleted) -174 bytes 0 bytes -100.0% 🗑️

@JSONbored JSONbored self-assigned this Jul 27, 2026
@JSONbored
JSONbored merged commit fbd1ed1 into main Jul 27, 2026
10 checks passed
@JSONbored
JSONbored deleted the fix/9060-9061-9084-cost branch July 27, 2026 04:27
@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.68%. Comparing base (0746e6c) to head (1108e82).
⚠️ Report is 3 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #9146      +/-   ##
==========================================
+ Coverage   90.56%   92.68%   +2.12%     
==========================================
  Files          96      815     +719     
  Lines       22490    80939   +58449     
  Branches     3884    24586   +20702     
==========================================
+ Hits        20367    75015   +54648     
- Misses       1945     4844    +2899     
- Partials      178     1080     +902     
Flag Coverage Δ
backend 93.49% <100.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/db/repositories.ts 96.76% <100.00%> (ø)
src/queue/ai-review-orchestration.ts 100.00% <100.00%> (ø)
src/review/adapters.ts 97.14% <100.00%> (ø)
src/review/public-stats.ts 98.97% <ø> (ø)
src/review/stats.ts 99.06% <ø> (ø)
src/selfhost/pg-dialect.ts 100.00% <100.00%> (ø)
src/services/ai-review.ts 97.40% <100.00%> (ø)
src/services/public-accuracy-trend.ts 100.00% <ø> (ø)

... and 711 files with indirect coverage changes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment