Skip to content

A raw TypeError: not a function leaks in the 400 envelope for {"title": 12345} — no code, no fields[] #7543

Description

@huangyiirene

Symptom

POST /api/v1/data/showcase_task with a number in a declared text field:

{"title": 12345}

answers:

400 { "error": "TypeError: not a function", "object": "showcase_task" }

— a raw JS runtime error as the client-facing message, with no code and no fields[]. Reproduced 3x.

Expected: the ledgered envelope the neighbouring invalid bodies produce on the same route in the same run:

bodyobserved
{}400 VALIDATION_FAILED with fields[]correct
bad enum value400 with invalid_option and the allowed listcorrect
{"title": 12345}400 "TypeError: not a function", no codedefect

Two separate contract breaks in one response: a handler-internal TypeError reaching the wire verbatim, and an error body outside the ledgered envelope shape (a client keying on code / 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 TypeError instead of producing a field error.

Investigation note from extraction (working tree checked 2026-08-11, so verify against a86db175 before acting): the observed body shape — status 400, raw message, an object key, and no code — matches no branch of mapDataError in packages/rest/src/rest-server.ts on current main. #5489 moved that mapper's terminal branch to a sanitised 500 INTERNAL_ERROR (no object key), the declared-4xx passthrough in resolveErrorResponse does not attach object, and every remaining 400 branch carries a code. Likewise validateOne in packages/objectql/src/validation/record-validator.ts handles a number in a text field 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 in packages/rest (envelope classification ⇒ domain:cli) or in packages/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

  1. Boot showcase on a fresh isolated file DB (SqlDriver / better-sqlite3); authenticate as admin@objectos.ai.
  2. POST /api/v1/data/showcase_task with body {"title": 12345} (a number into the declared text field) → 400 {"error":"TypeError: not a function","object":"showcase_task"}. Repeats 3/3.
  3. Controls on the same route: body {}400 VALIDATION_FAILED with fields[]; a bad enum value → invalid_option with the allowed list.

Source

Extracted from the QA run #7463 (framework a86db17).

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions