Repository navigation
A raw TypeError: not a function leaks in the 400 envelope for {"title": 12345} — no code, no fields[] #7543
Description
Activity
Triage:
domain:cliappended (keptpm:queue). The body's "label it once the throw site is found" condition is now met — the emitting seam is located, onorigin/main@34c01a5:- The throw:
examples/app-showcase/src/data/hooks/index.ts:34— hook sourceif (ctx.input.title) ctx.input.title = ctx.input.title.trim();. A numerictitle(12345) is truthy, has no.trim⇒TypeError. This is whyvalidateOne's safeString(value)never sees it — the hook throws first. - The wire shape:
packages/rest/src/rest-server.tsmapDataError, the sandbox-wrapper fallback (~:964-976): it strips thehook '<name>' threw:wrapper, deliberately keeps non-default error names (TypeError: …), and returns{ status: 400, body: { error: msg, object } }— nocode. That is byte-for-byte the observed response, and it answers the extraction note's "matches no branch ofmapDataError" (this branch was missed; it matches).
Both contract breaks the card names (raw runtime error verbatim + non-ledgered envelope without
code/fields[]) are produced by that onepackages/restbranch, and fixing it covers every hook-thrownTypeError, not just this repro ⇒ principal landingpackages/rest⇒domain:cli. The showcase hook's own type-unsafety (examples/**follows the subsystem it exercises) is a secondary hardening the dev can take in the same PR or leave.- Dup check: no open issue/PR names the sandbox-wrapper envelope or this repro; the sibling QA-wave cards (
POST /api/v1/automation/:name/toggleanswers 500 INTERNAL_ERROR instead of 404 NOT_FOUND for an unknown flow #7535 toggle-500, SECURITY:DELETE /api/v1/reports/:idanswers 500 for another owner's report but 204 for a nonexistent id — an enumeration oracle over report ids #7523 reports-oracle) are different routes/mechanisms./meta/:type上一个未分类的服务端故障被报成 HTTP 400 —— handleRouteError 的兜底把 outage 说成客户端错误 #5489 (mapper terminal branch) is closed history the card already cites. target:v17: not applied — error-path envelope quality on invalid input; no data loss/security/migration face, controls on the same route answer correctly.
本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
- The throw:
Claim — PM loop
domain:cli, sessionsession_0158ZQo7LiHSxGWpYKuPq1wu(os-help seat, #6024), wave 5.Unblocked: this was held behind #7525 because both touch the
/api/v1/dataerror boundary. #7525 landed minutes ago as PR #7575 (d6f3f2fe5).⚠️ That landing changes your starting point. #7575 rewrotemapDataError's explicit-status gate into a nameddeclaredHttpStatus()readingstatus→statusCodeover the 400–599 band. The card's tree read predates it. Re-measure on today'smainbefore concluding anything — and note that the card's most useful observation may now read differently.The card's investigation note is the most valuable thing in it, and it is a negative result: the observed body —
400, raw message, anobjectkey, nocode— matches no branch ofmapDataError. #5489 moved that mapper's terminal branch to a sanitised500 INTERNAL_ERRORwith noobjectkey; the declared-4xx passthrough inresolveErrorResponsedoes not attachobject; every remaining 400 branch carries acode. AndvalidateOneinpackages/objectql/src/validation/record-validator.tshandles a number in atextfield safely viaString(value). So the emitting seam is somewhere else, and finding it is step 1 — ⛔ do not assumemapDataErrorand start patching it.- Branch:
claude/issue-7543-typeerror-in-400-envelope ⚠️ Lane boundary, read this before you write code: the fix may land inpackages/rest(envelope classification ⇒ this lane) or inpackages/objectql(the coercion that throws ⇒domain:engine-core, not this lane). The extraction pass deliberately could not tell which. If the throw site turns out to be inpackages/objectql, stop and report the located seam rather than fixing it — that is a re-routing decision for triage/PM, not a scope you take unilaterally. Locating it precisely is a complete and valuable deliverable on its own.- Two contract breaks in one response, both must be answered: a handler-internal
TypeErrorreaching the wire verbatim, and a body outside the ledgered envelope (a client keying oncode/fields[]gets neither). - The controls are in the card and are the accept bar: on the same route,
{}→400 VALIDATION_FAILEDwithfields[], and a bad enum →invalid_optionwith the allowed list.{"title": 12345}must join them, not merely stop throwing. - ⛔ Serial constraints: two cards are live on
packages/rest/src/rest-server.ts— Three ledgered /meta routes are never mounted and die in the/meta/:typecatch-all — the route audit can't see this class because it treats the ledger as ground truth for what's mounted #7526 (/metaroute registration) and Public lookup route resolves the picker target from legacy field spellings only — omittedpublicPicker.objectanswers 500 against a canonical object schema #7486 (/forms/:slug/lookup/:field). The error-mapping region is now free (yours). - Tier / container: M ·
claude-opus-5·mode:cloud.
Generated by Claude Code
- Branch:
- added a commit that references this issue
on Aug 11, 2026 - added a commit that references this issue
on Aug 17, 2026 - added a commit that references this issue
on Sep 17, 2026 - added a commit that references this issue
on Sep 29, 2026
Symptom
POST /api/v1/data/showcase_taskwith a number in a declaredtextfield:{"title": 12345}answers:
— a raw JS runtime error as the client-facing message, with no
codeand nofields[]. Reproduced 3x.Expected: the ledgered envelope the neighbouring invalid bodies produce on the same route in the same run:
{}400 VALIDATION_FAILEDwithfields[]400withinvalid_optionand the allowed list{"title": 12345}400 "TypeError: not a function", nocodeTwo separate contract breaks in one response: a handler-internal
TypeErrorreaching the wire verbatim, and an error body outside the ledgered envelope shape (a client keying oncode/fields[]gets neither).Root cause
Not fully located in the report. Its suspicion: a coercion path calls a string method on a number before validation reports the type mismatch, so the write throws a
TypeErrorinstead of producing a field error.Investigation note from extraction (working tree checked 2026-08-11, so verify against
a86db175before acting): the observed body shape — status400, raw message, anobjectkey, and nocode— matches no branch ofmapDataErrorinpackages/rest/src/rest-server.tson current main. #5489 moved that mapper's terminal branch to a sanitised500 INTERNAL_ERROR(noobjectkey), the declared-4xx passthrough inresolveErrorResponsedoes not attachobject, and every remaining 400 branch carries acode. LikewisevalidateOneinpackages/objectql/src/validation/record-validator.tshandles a number in atextfield safely (String(value)), so the throw is upstream or downstream of it. Locating the emitting seam is step 1 of the fix.No
domain:*label applied deliberately: the fix could land inpackages/rest(envelope classification ⇒domain:cli) or inpackages/objectql(the coercion that throws ⇒domain:engine-core), and the extraction pass could not tell which from the report plus a tree read. Label it once the throw site is found.Reproduction
SqlDriver/ better-sqlite3); authenticate asadmin@objectos.ai.POST /api/v1/data/showcase_taskwith body{"title": 12345}(a number into the declaredtextfield) →400 {"error":"TypeError: not a function","object":"showcase_task"}. Repeats 3/3.{}→400 VALIDATION_FAILEDwithfields[]; a bad enum value →invalid_optionwith the allowed list.Source
Extracted from the QA run #7463 (framework a86db17).