Repository navigation
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
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 outcomesisExpectedQueryRejection(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"
concernplugin-audit'sprovisionSystemTablesdocblock 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-admininexamples/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.
Activity
baozhoutao commentedon Aug 5, 2026
分诊(cli 车道 PM,session_01VkPSGsX9o17MsGv3Lbxu2w,2026-08-05,step 0 sweep):入队
pm:queue。具名位置(rest-server.ts29 处无条件logError,draft-read 路径 ~L3923)+ 既有守卫(isExpectedDataStatus/isExpectedQueryRejection)+ 明确修向(共享handleRouteErrorhelper,两族路由同门)—— 属「有具名位置的具体缺陷」,无需上升。非 v17 窗口件,按车道容量排在 v17 工作集之后。维护者可否决。Generated by Claude Code
baozhoutao commentedon Aug 5, 2026
🔒 认领:PM 循环第 3 轮(cli 车道)
会话:
session_01VkPSGsX9o17MsGv3Lbxu2w分支:
claude/issue-4886-rest-expected-404-loggingWorktree:
objectstack-issue-4886域:
domain:cli文件面:
packages/rest/src/rest-server.ts+ 测试 + changeset。与同批 #5085(runtime auth 域)、#5225(examples flow)不相交。执行口径:按 issue 建议做共享
handleRouteError(res, error)helper,metadata 与 data 两族路由同门(29 处无条件logError收敛),预期 4xx(NO_DRAFT及同类)不再打「Unhandled error」+ 栈;isExpectedQueryRejectiondocblock 记载的上一次同型回归是判据来源。注意rest-server.ts近日被 #5362(zodIssuesToFields)触及,以 origin/main 为准。Generated by Claude Code
baozhoutao commentedon Aug 5, 2026
✅ 验收通过 —— PR #5394,转 ready 挂合并队列(队列等全绿收单)
对实况复核(cli 车道 PM,session_01VkPSGsX9o17MsGv3Lbxu2w):
logError在 fix(rest): zodIssuesToFields 展开 invalid_union,联合分支里的处方到达调用方 (#5014) #5362 后仍成立,热点行号漂移(~L3762)已记录。resolveErrorResponse从sendError拆出使日志判断读响应本身;isExpectedRouteError成为唯一谓词,旧的isExpectedDataStatus/isExpectedQueryRejection只剩它一个调用者 —— docblock 里记载的上一轮漂移(filter/sort code)从机制上不可再发生。谓词刻意不放宽到「任何 4xx」:无 code 400 是 handler bug 的落点,保持响亮 —— 正确避开了镜像缺陷。tsc --noEmit基线对比,0 新增;runtime 套件改前后逐字节一致(容器网络性既有失败,与本改动无关)。sendError属@objectstack/types不同物;五个兄弟路由面无同类缺陷 ——out_of_scope_findings为空是巡检过的空,不是没巡。CI 完成项由 auto-merge 等待收单。跟到 MERGED。
Generated by Claude Code
fix(rest): route expected 4xx through one shared handleRouteError, no…
feat(rest): a declared OS_REST_LOG fault-log level seam, shipped defa…