What happens
Opening Studio / any metadata designer floods the server log with stack traces:
[REST] Unhandled error: Error: [no_draft] No pending draft exists for app/showcase_app.
at _ObjectStackProtocolImplementation.getMetaItem (…/metadata-protocol/dist/index.js:2656:21)
at async handler (…/rest/dist/index.js:…) {
code: 'NO_DRAFT',
status: 404
}
45 of these in a single browsing session across ~60 console routes — by far
the dominant entry in the log. Breakdown by target:
| count |
target |
| 14 |
app/showcase_app |
| 8 |
page/showcase_capability_map |
| 4 |
object/showcase_account |
| 4 |
app/setup |
| 2 |
page/showcase_start_here, object/showcase_project |
| 1 each |
object/showcase_task, dataset/showcase_task_metrics, dashboard/showcase_ops_dashboard, flow/showcase_task_completed, permission/showcase_contributor, … |
This is a normal outcome, not a fault
The designer probes GET /meta/:type/:name?state=draft to decide whether to show
"unsaved draft" state. Not having a draft is the overwhelmingly common case —
it is true of every artifact nobody is currently editing.
The wire behaviour is already correct: getMetaItem throws a structured error
carrying code: 'NO_DRAFT' and status: 404, sendError passes it through, and
the client gets a clean 404 it handles fine (no user-visible breakage). Only the
logging is wrong.
The guard already exists — metadata routes just don't use it
rest-server.ts has exactly this concept for the data routes:
isExpectedDataStatus(status) (~L627) — 403/404/409/502/503 are expected outcomes
isExpectedQueryRejection(body) (~L642) — client-caused 400s
and the CRUD handlers consult them:
if (!isExpectedDataStatus(mapped.status) && mapped.body?.code !== "VALIDATION_FAILED")
logError("[REST] Unhandled error:", error);
The metadata handlers do not. There are 29 unconditional
logError("[REST] Unhandled error:", error) sites in rest-server.ts; the
draft-read path at ~L3923-3932 is the one Studio hits on every panel.
The isExpectedQueryRejection docblock already records this exact regression
happening once before:
The filter and sort codes joined this list late: both shipped without it, so
every rejection they produced was ALSO logged as an unhandled error.
Same shape, different route family.
Why it matters
- It buries real errors. 45 stack traces of noise per session is how a
genuine 500 goes unnoticed — the "log is noisy and can mask real errors"
concern plugin-audit's provisionSystemTables docblock raises verbatim.
- It misreports severity. "Unhandled error" + stack trace tells an operator
something is broken. Nothing is.
- It is also per-request work (stack formatting) on a hot UI path.
Suggested direction
Route the metadata handlers' catch through the same predicate the data handlers
use, so a structured error carrying an expected 4xx (NO_DRAFT and its siblings)
responds without an "Unhandled error" line. Given there are 29 unguarded sites, a
single shared handleRouteError(res, error) helper that both families call would
stop the two from drifting apart again — the docblock above shows they already
have once.
Repro
os dev --ui --seed-admin in examples/app-showcase
- Sign in, open
/_console/studio/com.example.showcase/interfaces
- Watch the server log — one stack trace per designer panel
Observed on main @ 0e96e46. Found while browser-sweeping showcase + Studio for #4879.
What happens
Opening Studio / any metadata designer floods the server log with stack traces:
45 of these in a single browsing session across ~60 console routes — by far
the dominant entry in the log. Breakdown by target:
app/showcase_apppage/showcase_capability_mapobject/showcase_accountapp/setuppage/showcase_start_here,object/showcase_projectobject/showcase_task,dataset/showcase_task_metrics,dashboard/showcase_ops_dashboard,flow/showcase_task_completed,permission/showcase_contributor, …This is a normal outcome, not a fault
The designer probes
GET /meta/:type/:name?state=draftto decide whether to show"unsaved draft" state. Not having a draft is the overwhelmingly common case —
it is true of every artifact nobody is currently editing.
The wire behaviour is already correct:
getMetaItemthrows a structured errorcarrying
code: 'NO_DRAFT'andstatus: 404,sendErrorpasses it through, andthe client gets a clean 404 it handles fine (no user-visible breakage). Only the
logging is wrong.
The guard already exists — metadata routes just don't use it
rest-server.tshas exactly this concept for the data routes:isExpectedDataStatus(status)(~L627) — 403/404/409/502/503 are expected outcomesisExpectedQueryRejection(body)(~L642) — client-caused 400sand the CRUD handlers consult them:
The metadata handlers do not. There are 29 unconditional
logError("[REST] Unhandled error:", error)sites inrest-server.ts; thedraft-read path at ~L3923-3932 is the one Studio hits on every panel.
The
isExpectedQueryRejectiondocblock already records this exact regressionhappening once before:
Same shape, different route family.
Why it matters
genuine 500 goes unnoticed — the "log is noisy and can mask real errors"
concern
plugin-audit'sprovisionSystemTablesdocblock raises verbatim.something is broken. Nothing is.
Suggested direction
Route the metadata handlers' catch through the same predicate the data handlers
use, so a structured error carrying an expected 4xx (
NO_DRAFTand its siblings)responds without an "Unhandled error" line. Given there are 29 unguarded sites, a
single shared
handleRouteError(res, error)helper that both families call wouldstop the two from drifting apart again — the docblock above shows they already
have once.
Repro
os dev --ui --seed-admininexamples/app-showcase/_console/studio/com.example.showcase/interfacesObserved on
main@ 0e96e46. Found while browser-sweeping showcase + Studio for #4879.