Skip to Content

v0.1.89 — 5 Aug 2026

Changed — meta.total is estimated by default

Who is affected: Every consumer of list endpoints that reads meta.total — most visibly on large collections. Anyone who needs an exact total must now ask for it.

Behaviour change. meta.total on GET /api/items/[collection] is no longer guaranteed exact. Small results keep exact totals; large ones return the query planner’s estimate. Pass ?count=exact where the number must be precise.

What was wrong

Every list read ran PostgREST’s count=exact alongside the page query. An exact count cannot be answered without visiting every matching row, so on large collections the count dominated the request:

WorkCost per request (5M-row collection)
The 25-row page~0.10 ms
The exact count~139 ms

The count was 99.9% of the database work, and it set the throughput ceiling for the whole platform.

What changed

The count mode is now selectable per request with ?count=, defaulting to estimated:

?count=Behaviour
estimated (default)Exact for small results, the planner’s estimate for large ones. Measured 0.15 ms on the same 5M-row table, with the total off by ~1.3%
plannedAlways the planner’s estimate. Constant cost, approximate
exactAlways precise. Pay the full-scan cost only where the number matters — exports, reconciliation

Internally, ItemsService.readByQueryWithCount takes the same countMode option with the same default, so the users, roles, and paginated scope listings also stop paying for exact counts on large tables.


Performance — internal reads no longer pay for a row count nobody reads

Who is affected: Requests that go through the cron job, runtime extension, custom service, or scope read paths, and the scope MCP tool — most visibly on large collections.

What was wrong

v0.1.88 taught readByQuery to skip the row count, but nine internal call sites were still calling readByQueryWithCount and then discarding the count it returns. Each of those reads ran PostgREST’s count=exact — a second full pass over the filtered set — to produce a number nobody read.

What changed

Those call sites now use readByQuery, which never requests a count. Both methods run the same permission, filter, search, and relation pipeline, so the rows returned are identical — the reads just stop paying for the extra pass.

Converted reads:

  • scope MCP tool: read_items, read_user_roles, and the user-role removal lookup
  • cron job listing (getCronJobs)
  • runtime extension listing (getExtensions)
  • custom service listing (getCustomServices) and name lookup (getCustomServiceByName)
  • scope collection config listing (getConfigs)
  • the child-existence check in deleteScopeItem

Call sites that actually return the count — the paginated scope item and scope type listings — still request it.


Internal — Claude Code agents and skills

Agent definitions (code reviewer, security auditor, test engineer, web performance auditor) and skills (code review & quality, context engineering, debugging & error recovery, doubt-driven development) were added under .claude/ for AI-assisted development. Repository tooling only — no runtime behaviour changes.

Last updated on