Skip to content

Metadata routes log every expected 404 as "[REST] Unhandled error" with a stack trace — Studio's draft probe produced 45 in one browsing session #4886

Description

@xuyushun441-sys

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

  1. os dev --ui --seed-admin in examples/app-showcase
  2. Sign in, open /_console/studio/com.example.showcase/interfaces
  3. Watch the server log — one stack trace per designer panel

Observed on main @ 0e96e46. Found while browser-sweeping showcase + Studio for #4879.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions