Skip to content

Malformed JSON request body is silently parsed as {}: a truncated POST /data/:object/query answers 200 with the whole table, and a truncated PATCH answers 200 with an empty update #23914

Closed

Description

@objectstack-fleet

Filing gate ①: a product defect with a named landing and a reproduction. Reach: the public HTTP door POST /api/v1/data/:object/query, measured live twice by two independent agents on main 94114c1f.

Reader who acts: the REST/transport lane seat owning packages/plugins/plugin-hono-server and packages/adapters/hono. An unparseable JSON body must be refused with a 400 at the transport, never handed on as {}.

What

When a request declares Content-Type: application/json and the body is not valid JSON, the HTTP layer swallows the parse error and runs the handler with body = {}. On the query door this is the #3899 silent-widening class: a malformed filter answers 200 with the whole table instead of a 400. On the write door, a malformed PATCH answers 200 and performs an empty update.

Reproduction (fresh objectstack dev --seed-admin showcase boot, admin session)

Send each body from a file (curl -H 'Content-Type: application/json' --data-binary @b.txt …) so shell quoting plays no part.

  1. POST /api/v1/data/showcase_account/query with {"where": {"status": "churned"}} → 200, 2 rows. Correct.
  2. Same request with the closing brace missing: {"where": {"status": "churned"}
    • expected: 400 (an invalid-JSON / validation code);
    • actual: 200 with every row (14 of 14 on a fresh DB; 26 of 26 on a DB with more seed).
  3. { not json → 200, every row.
  4. A valid where-body sent as text/plain → 200, every row. parseBody() yields {} and the filter is dropped.
  5. PATCH /api/v1/data/showcase_account/ID with {"name": "HACKED" (truncated)
    • expected: 400;
    • actual: 200, with no field changed and updated_at bumped. The client is told a write succeeded that never happened.
  6. POST /api/v1/data/showcase_account with a truncated body containing name → 400 VALIDATION_FAILED "Account Name is required". The error is misleading: the body was discarded, and no parse error is named.

Mechanism

  • packages/plugins/plugin-hono-server/src/adapter.ts (~L691–710): let body = {}; on application/json, try { body = await c.req.json() } catch { try { body = await c.req.parseBody() } catch {} }. Both failures are swallowed.
  • packages/adapters/hono/src/index.ts (~L322–329) readDispatchBody: try { return JSON.parse(await raw.clone().text()) } catch { return {} }. The same behaviour on the second transport.
  • packages/runtime/src/domains/meta.ts (~L1314) documents a c.req.json().catch(() => ({})) catch-all.
  • No HTTP handler emits an invalid-JSON code; a grep for INVALID_JSON / "Malformed JSON" finds only CLI and driver code. A new code needs an ADR-0112 ledger row.

Contract it violates

docs/qa/platform-checklist/areas/api-backend.json → api-backend.query-contract-matrix (P0), negative: "silent WIDENING is the catastrophic failure mode (#3899): a malformed query body must never degrade into an unfiltered 200 full read". Item title: "malformed input 400s with the exact code".

Dedupe

MCP search_issues on objectstack-ai/objectstack, closed included:

No existing card covers this.

QA-source: #23913 · api-backend.query-contract-matrix · clause 6 (silent-widening negative)

Activity

  1. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, bug · priority:p1 · target:v18 · domain:cli · area:api · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-16T12:48Z

    Triage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.

    • Re-read on origin/main d5666be876: packages/plugins/plugin-hono-server/src/adapter.ts:691 opens let body: any = {}; before the swallowed c.req.json() / parseBody() pair. The filed reading holds.
    • Why domain:cli: plugin-hono-server and packages/adapters/* are domain:cli by the domain table.
    • Why p1 + target:v18: refusing an unparseable body with a 400 narrows the REST / data door, which the maintainer's protocol-first rule makes p1 + target:v18 in every lane. The measured faces are also wrong answers at a public door:
      • a truncated query answers 200 with every row the caller can read;
      • a truncated PATCH answers 200 for a write that never happened.
    • For the claimant: cover every adapter that shares the swallow, not just plugin-hono-server (packages/adapters/hono, and any sibling with the same catch {}). Steps 4 (text/plain) and 6 (the misleading VALIDATION_FAILED) are part of the done line.
  2. added
    area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobs
    bugSomething isn't working
    priority:p1High: required for production / M2
    and removed on Oct 16, 2026
  3. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (#23914: a request whose body cannot be parsed is refused with a 400 at the transport, never handed on as {}. A truncated POST /data/:object/query stops answering 200 with the whole table, and a truncated PATCH stops answering 200 for a write that never happened) · 2026-10-16T15:45Z
    Session: session_018t6a5rvqPRvEDMCVVW9kYT
    Account: os-tesla (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: claude/issue-23914-malformed-body-refusal
    Worktree: objectstack-issue-23914
    Domain: domain:cli
    Seat: domain:cli#2 (seat post #22648)
    Scope: batch 3, counting dev runs. In flight before this act: #23050 slice 10 (6218443248) and #23181 slice 4 (6218822258). #15953 slice 7 (PR #23976) and #23858 (PR #23979) are in landing.
    Order, read at this act: this card is the lane's only open, unclaimed p1 (target:v18, graded 2026-10-16T12:48Z). #23948 (p0) and the other p1 cards are seat 1's or hotlong's.
    File surface (read on origin/main 2054a0888c; stop on a breach and explain it in the report):

    • packages/plugins/plugin-hono-server/src/adapter.ts, the body parse only (about :691–:712, the c.req.json() / parseBody() pair whose failures are swallowed).
    • packages/adapters/hono/src/index.ts, readDispatchBody only (about :322–:329, its two {} fallbacks).
    • packages/runtime/src/domains/meta.ts, the c.req.json().catch(() => ({})) catch-all (about :1314), only if measurement shows a request reaches it with a declared JSON body.
    • Any sibling with the same swallow (triage's ask): the claimant greps every transport and handler in this lane for .json().catch(, catch {} around a body parse and return {} from a body reader, and classifies each. In scope: a door that takes a client body. Out: a reader of a body this platform wrote itself, with the reason.
    • The refusal: 400 at the transport, with an EXISTING ERROR_CODE_LEDGER code. INVALID_REQUEST is on the ledger. ⛔ No new code: a new code is Clause-②: yes and needs a ledger row. If no existing code fits, stop and report it as needs_decision.
      • A declared application/json body that does not parse is refused.
      • The card's step 4, a JSON body sent as text/plain, and step 6, the misleading VALIDATION_FAILED on a discarded body, are in the done line, as triage requires.
      • An absent or empty body stays as it is today. The claimant measures and states which doors treat an empty body as {} legitimately.
    • Pins: the card's six reproduction steps at the door (truncated query, { not json, text/plain, truncated PATCH, truncated create), each refused 400 naming the parse failure. CONTROL: a valid body answers as before on each door, and so does an empty body where it is legitimate.
    • One .changeset/23914-*.md (patch or as the narrowing rule requires, for each package whose shipped behaviour changes), carrying the Clause-② line below.
    • ⛔ Hold-offs: no new error code or ledger row; no handler-level validation change beyond the parse refusal; nothing in packages/rest/src/rest-server.ts ([maintainer] refactor(rest): split packages/rest/src/rest-server.ts (13,701 lines, 70 commits in 30 days) along its register*Endpoints seams, one move-only slice per PR, /meta after #22877 stage 2 #23050 slice 10 is moving its UI section).
      Container & model: M (one refusal across two transports and their siblings, a narrowing of a published door, measured at the door), mode:subagent, model: TIER_DEFAULT for the build. The narrowing face owes a CONTRACT_REVIEW_TIER review before the queue (below).
      Clause-②: no (narrowing)
      Basis for the Clause-② line:
    • Today the REST and data doors accept an unparseable body and answer as if it were {}. After this card they refuse it with a 400. That narrows the accepted inputs of a published door, as triage's grade says ("refusing an unparseable body with a 400 narrows the REST / data door").
    • No new error code: INVALID_REQUEST is already on the ledger. No packages/spec edit.
    • By references/contract-review.md, a (narrowing) declaration owes one CONTRACT_REVIEW_TIER review, served by this seat before the queue.
      Responsibility: the HTTP transports in this lane (plugin-hono-server, adapters/hono) and the runtime's meta domain | the api-backend checklist item api-backend.query-contract-matrix (P0): "a malformed query body must never degrade into an unfiltered 200 full read"; #3899's silent-widening class | every client whose request body is cut short or mis-typed: a query reads the whole table and a write reports success it did not make
      Thread-read: 6215847310
      Serial constraints cleared: every open PR's file list was read at this act; none touches the three files above. Seat 1's os migrate organization-ownership never certifies a database whose packages declare sharing rules: it waits on the declared-rule-mirrors departure (D2) while the same release's boot still seeds them #23948 holds packages/cli/src/utils/organization-ownership-* and its command, which are disjoint. No pm:dispatched claim names plugin-hono-server, adapters/hono or runtime/src/domains/meta.ts.

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 23914,
      "status": "done",
      "branch": "claude/issue-23914-malformed-body-refusal",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/24005",
      "session": "session_018t6a5rvqPRvEDMCVVW9kYT",
      "premise_still_valid": true,
      "summary": "A request body an HTTP door cannot parse is now refused 400 INVALID_REQUEST (an existing ERROR_CODE_LEDGER code, no new row), never handed on as {}: plugin-hono-server refuses when a handler reads req.body (malformed declared JSON, malformed form, or a non-empty body in a media type it does not read, so the text/plain JSON of step 4 too), createHonoApp refuses a malformed declared-JSON body before dispatch() on both arms, and cloud-connection's four own routes refuse the same way. Measured at a fresh objectstack dev showcase door, base 2054a0888c vs final head b9df5b1d3: steps 2-4 went from 200 with 14 of 14 rows to 400; step 5 from 200 with updated_at bumped to 400 with the record untouched; step 6 from 400 VALIDATION_FAILED 'Account Name is required' to the parse refusal; every CONTROL (valid body, empty body, no body, valid PATCH/create, raw upload of a .txt as text/plain and of a JSON-with-comments file as application/json) answers as before. PM mechanism assumption 3 was falsified and moved the design: the storage presign tells clients to PUT a .json file as application/json and the raw-PUT route reads rawBody(), so plugin-hono-server raises the refusal when req.body is READ (stopping the handler there; the transport's 400 is the answer whatever the handler made of the throw), not before the handler runs. Three minor changesets with 'Clause-②: no (narrowing)' and an ADR-0087 not-required (no-migration-prescription) disposition; the seat's CONTRACT_REVIEW_TIER review is owed before the queue.",
      "tests": "All under scripts/pm/os-verify-lock.sh, exit codes captured before any pipe. FINAL HEAD b9df5b1d3: pnpm --filter @objectstack/plugin-hono-server exec vitest run --maxWorkers=2 :: exit 0 (30 files, 386 passed); pnpm --filter @objectstack/hono exec vitest run --maxWorkers=2 :: exit 0 (9 files, 176 passed); pnpm --filter @objectstack/cloud-connection exec vitest run --maxWorkers=2 :: exit 0 (46 files, 561 passed); pnpm --filter X typecheck :: exit 0 for all three (plugin-hono-server incl. check:test-typecheck OK; hono and cloud-connection tsc --noEmit + -p tsconfig.test.json). Post-merge refresh: pnpm install --frozen-lockfile :: exit 0; pnpm turbo run build --filter='!@objectstack/docs' --concurrency=2 :: exit 0 (75 tasks); pnpm --filter @objectstack/spec check:generated :: exit 0. New pins: plugin-hono-server/src/request-body-refusal.test.ts (20: card bodies refused with envelope parsed against ApiErrorSchema/envelopeViolations, handler never acts, no echo, handler's own 500 catch loses to the refusal, fallback seam both ways, real-socket step 4; CONTROL valid/empty/no-body/form/valid PATCH/3 byte doors stored byte for byte), adapters/hono/src/hono-body-refusal.test.ts (15: refusals with dispatch() never called, /auth/* fallback, CONTROL, cross-door parity HonoHttpServer vs createHonoApp equal status+body), cloud-connection error-envelope.conformance (4 refusals + empty-body CONTROL) and install-modes (refusal with zero writes + CONTROL); fixture triage: hono-catch-all-raw-request.test.ts's ['invalid JSON','application/json','{ not json',{}] row pinned the removed branch and was replaced. DOOR (objectstack dev --fresh showcase, curl --data-binary from files): base 2054a0888c step2/3/4 200 rows=14, step5 200 updated_at 16:06:38.737Z -> 16:07:18.807Z name unchanged, step6 400 VALIDATION_FAILED; head 0660b5a16 and final b9df5b1d3 step2/3/4/5/6 400 INVALID_REQUEST 'The request body could not be parsed as JSON (it was sent as application/json)...' / 'The request body was sent as text/plain, which this server does not read into a request body...', step5 updated_at unchanged; CONTROLs identical on both; also b9df5b1d3: cloud-connection/bind/poll truncated 400 parse refusal, {} 400 'device_code is required', marketplace/install-local truncated 400 parse refusal; 0660b5a16: PUT /meta/object/showcase_account and POST /keys truncated 400 parse refusal. ABLATION (scripts/ablation-replace.mjs WRAP, anchor x1 -> x0, subjects resolve from src so no rebuild): plugin-hono-server accessor refusal -> return {} (blob a331a0f6ed81 -> dee7fcca5045): 10 failed (every refusal, fallback-reads, socket step 4) / 10 passed (every CONTROL); hono declared-JSON throw deleted (b3d7844bbd26 -> 0a88eaa0cb2f): 9 failed / 6 passed; cloud-connection parse refusal -> {ok:true, body:{}} on 2d952cae1 (61561f4f3e17 -> 7c27524a66d0): 4 failed / 41 passed. Restore proven each run: blob == HEAD blob, git diff HEAD 0 bytes, git status --porcelain 0 lines, marker count 0.",
      "gates": "LOCAL on b9df5b1d3: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (merge base adce19297, no stale-tree warning) derived 66 families; all 66 run, every one :: exit 0 (incl. pnpm check:route-envelope, check:dispatcher-error-vocabulary OK, check:error-status-conformance, check:nul-bytes OK, check:published-files, check:dts-closure, check:test-source-alias, check:type-check-coverage, check:adr-0087-registration '3 declared-breaking changeset(s), each carrying an ADR-0087 disposition', check:empty-changeset, check-changeset-no-major); --ran reconciliation with 'COMMAND :: exit CODE' records :: exit 0, '66 derived, 66 run, 0 NOT-MEASURED, 0 UNRUN', all 66 carrying an exit code. pnpm exec eslint --no-inline-config over the 11 changed .ts files :: exit 0, no findings. First union on 0660b5a16 had check:route-envelope :: exit 1 (new cloud-connection helper built a response; the gate wanted a PLUGIN_ROUTE_MODULES row in scripts/check-route-envelope.mjs, outside the file surface) -> helper refactored to return a message, each declared route module builds its own 400 body -> exit 0. CI (PR #24005 head b9df5b1d3, read once after open, not waited on): 23 check-runs; 'No other open PR may claim the same issue' success, 'Packed-tarball smoke (opt-in)' skipped, all others in_progress: Check Changeset, Lint · ESLint, Lint · CI, release & engine gates, Lint · PM dispatch-gates, Lint · docs & code guards, Lint · repo & tooling gates, Lint · scoped · query-options erasure, Lint · scoped · slot lookup & corpus, Lint · scoped · tooling self-tests, Type Check · workspace, Type Check · source gates, Type Check · consumer gates, Type Check · debt ledger, Governed Surface Queue Guard, Check PR Size, Check Documentation Links, Flag docs affected by code changes, The card this PR closes must claim this branch, Part-of PR must not also close its card, No other open PR may claim the same single-writer path, filter. NOT MEASURED: Lint & Repo Gates / Test Core / Build Core aggregate contexts, reason: not yet reported at the read.",
      "mcp_calls": "0 — no MCP GitHub tool used (reads were gh api GETs: issue 23914 + comments, pulls/24005, its check-runs)",
      "api_writes": "4 REST writes via the fleet-write relay as objectstack-fleet[bot], in 2 dispatch strokes (POST /repos/objectstack-ai/objectstack/dispatches x2): stroke 1 pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft forced) + POST /repos/objectstack-ai/objectstack/issues/24005/assignees ['os-tesla']; stroke 2 = POST /repos/objectstack-ai/objectstack/issues/23914/comments (this report). label-write: none (per dispatch). git push (not REST) for the branch.",
      "deviations": [
        "PM mechanism assumption 3 FALSIFIED and the route changed per the ruling's intent: the storage raw-PUT door relies on the transport not refusing (its presign answers headers content-type application/json for a .json file; objectui UploadProvider PUTs Content-Type: mimeType; dogfood suites PUT text/plain). plugin-hono-server therefore raises the 400 when a handler READS req.body, not before the handler runs; still the transport's 400 with an existing code, no handler change. createHonoApp refuses eagerly (no byte door there; forwarding domains read form/own bytes) but only declared JSON.",
        "Assumption 1 measured: the objectstack dev door is plugin-hono-server (HonoServer); createHonoApp is the embed door, mounted by nothing in packages/cli; both carried the swallow, both changed. Assumption 2 decided by the documented contract (content/docs/api/wire-format.mdx: Content-Type application/json required): text/plain is refused on plugin-hono-server, not parsed; createHonoApp's pre-existing content-type-blind parse is left unchanged (Acceptance note) — no needs_decision.",
        "File surface: beyond the two transports, the in-lane siblings under the claim's sibling clause — cloud-connection-plugin.ts (/bind/start, /bind/poll, /install), marketplace-install-local-plugin.ts (handleInstall), new module-private cloud-connection/src/request-json-body.ts, plus tests. runtime/src/domains/meta.ts untouched (its :1314 is a comment, not a body reader). scripts/check-route-envelope.mjs NOT edited: the gate's red was resolved inside the surface by refactoring the helper.",
        "Merged origin/main (adce19297, 9 commits, none touching the three packages) before opening the PR per AGENTS.md §10, with the §9 refresh; door run, suites, typechecks and the gate union re-taken on the final head b9df5b1d3.",
        "Changesets are minor (launch-window convention for a breaking narrowing), not patch; three files, one per package.",
        "Attribution: commit trailers are AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude), not the harness reminder's model-named Co-Authored-By (pre-push refuses a model identifier); the PR body ends with AGENTS.md's single session-URL footer instead of the harness's two lines.",
        "test:repo (vitest repo project) not run locally: the diff touches no repo-project test and the derivation does not name it; CI Test Core owns it. packages/cli integration tier: not applicable (no cli change).",
        "The refusal message lives in three module-private copies (plugin-hono-server, hono, cloud-connection): a shared export would enlarge a package's public surface, which this card's Clause-② no (narrowing) does not declare; the hono parity test holds the two transports equal."
      ],
      "files_changed": [
        ".changeset/23914-cloud-connection-body-refusal.md",
        ".changeset/23914-hono-body-refusal.md",
        ".changeset/23914-hono-server-body-refusal.md",
        "packages/adapters/hono/src/hono-body-refusal.test.ts",
        "packages/adapters/hono/src/hono-catch-all-raw-request.test.ts",
        "packages/adapters/hono/src/index.ts",
        "packages/cloud-connection/src/cloud-connection-plugin.ts",
        "packages/cloud-connection/src/error-envelope.conformance.test.ts",
        "packages/cloud-connection/src/marketplace-install-local-install-modes.test.ts",
        "packages/cloud-connection/src/marketplace-install-local-plugin.ts",
        "packages/cloud-connection/src/request-json-body.ts",
        "packages/plugins/plugin-hono-server/src/adapter.ts",
        "packages/plugins/plugin-hono-server/src/request-body-refusal.test.ts",
        "packages/plugins/plugin-hono-server/src/request-body.ts"
      ],
      "open_questions": [],
      "out_of_scope_findings": [
        "class: a · reach: POST /api/v1/auth/admin/unlock-user with the truncated body {\"userId\": \"usr_nobody\" sent as application/json answers 400 INVALID_REQUEST 'userId is required' (measured at a fresh objectstack dev showcase boot, admin session, on 0660b5a16; plugin-auth unchanged at b9df5b1d3) · evidence: plugin-auth's own routes keep the swallow this card removed elsewhere — try { body = await c.req.json() } catch { body = {} } at auth-plugin.ts:2735 (admin/oauth2/toggle-disabled), :2870 (admin/unlock-user), :3423 (sys-oauth-application/register), admin-import-users.ts:493, register-sso-provider.ts:250/:444/:501 — the step-6 shape (a misleading required-field refusal for a body that carried the field), not widening; lane is plugin-auth, not domain:cli · same family as #23914: name it for that family's closing card, not a point card · dedupe words: plugin-auth malformed body; c.req.json catch body {}; userId is required truncated body; unparseable request body auth routes",
        "carrier: none (承接者:无) · createHonoApp reads every content type as JSON: a JSON body sent as text/plain is parsed and a non-JSON body that does not declare JSON is {}; refusing it needs the dispatcher to know which domains forward the raw request (the /approvals/act form, inbound hooks) · noted in PR Acceptance notes, not filed",
        "carrier: none (承接者:无) · @objectstack/http-conformance NodeHttpServer (private, ships nothing) keeps catch { body = {} } · noted in PR Acceptance notes, not filed",
        "carrier: none (承接者:无) · runtime/src/domains/meta.ts:1314's comment still quotes the catch-all's pre-#22576 line c.req.json().catch(() => ({})); its subject (falsy JSON values arrive falsy) still holds · noted in PR Acceptance notes, not filed"
      ],
      "cleanup": "dev servers on 38914/38915/38916 started and stopped by recorded PID (ports free); no background job or monitor left running; after this comment's read-back the worktree's node_modules and the worktree ../objectstack-issue-23914 are removed (branch fully pushed at b9df5b1d3, tree clean)."
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 23914,
      "status": "done",
      "branch": "claude/issue-23914-malformed-body-refusal",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/24005",
      "session": "session_018t6a5rvqPRvEDMCVVW9kYT",
      "addendum": "fix round for PR #24005's Test Core (3/6) red, with seat ruling B (contract review 6221858698) applied; fields not listed are unchanged from comment 6221346264",
      "head": "97d881a2e (pushed; plain pushes on top of b9df5b1d3, no rebase, no force, no merge: merge-clean against origin/main)",
      "files_changed": [
        ".changeset/23914-cloud-connection-body-refusal.md",
        ".changeset/23914-hono-body-refusal.md",
        ".changeset/23914-hono-server-body-refusal.md",
        "packages/adapters/hono/src/hono-body-refusal.test.ts",
        "packages/adapters/hono/src/hono-catch-all-raw-request.test.ts",
        "packages/adapters/hono/src/index.ts",
        "packages/cloud-connection/src/cloud-connection-plugin.ts",
        "packages/cloud-connection/src/error-envelope.conformance.test.ts",
        "packages/cloud-connection/src/marketplace-install-local-install-modes.test.ts",
        "packages/cloud-connection/src/marketplace-install-local-plugin.ts",
        "packages/cloud-connection/src/request-json-body.ts",
        "packages/plugins/plugin-hono-server/src/adapter.ts",
        "packages/plugins/plugin-hono-server/src/request-body-refusal.test.ts",
        "packages/plugins/plugin-hono-server/src/request-body.ts",
        "packages/qa/http-conformance/src/hono-body-refusal.conformance.test.ts"
      ],
      "tests": "REPRO at b9df5b1d3 (closure built, under the lock): pnpm --filter @objectstack/http-conformance exec vitest run --maxWorkers=2 :: exit 1, 1 failed | 143 passed; file src/hono-webhooks-redeliver.conformance.test.ts, case 'status, code, headers and body bytes equal at both doors (the row id masked)', first assertion line 273, row 'not JSON': hosted body {\"success\":false,\"error\":{\"code\":\"INVALID_REQUEST\",\"message\":\"The request body could not be parsed as JSON (it was sent as application/json)...\"}} vs self-hosted mount {\"success\":false,\"error\":{\"code\":\"INVALID_REQUEST\",\"message\":\"Request body must be JSON.\"}}. Same case at merge base adce19297 (detached compare worktree): the file :: exit 0, 7 passed. ROOT CAUSE: createHonoApp refused before dispatch(), pre-empting the forwarding /webhooks/redeliver member (which reads its own bytes after its own sign-in check). FIX (ruling B): createHonoApp hands dispatch() a body whose every read throws RequestBodyRefusal and records the read; both arms answer the refusal only if it was read. The row stays as pinned (an interim local re-pin was reverted before any commit, blob == HEAD proven). RUNS on 0cee7c3d9 (= the code of 97d881a2e; its last commit only rewrites the hono changeset): pnpm --filter @objectstack/hono exec vitest run --maxWorkers=2 :: exit 0 (9 files, 180 passed); pnpm --filter @objectstack/http-conformance exec vitest run --maxWorkers=2 :: exit 0 (14 files, 150 passed; the redeliver row green as pinned); pnpm --filter @objectstack/hono typecheck :: exit 0; pnpm --filter @objectstack/http-conformance typecheck :: exit 0 (check:test-typecheck OK). BASE adce19297 full suite: pnpm --filter @objectstack/http-conformance exec vitest run --maxWorkers=2 :: exit 0 (13 files, 144 passed). plugin-hono-server and cloud-connection are byte-identical to b9df5b1d3: their suites there stand (386 / 561 passed). NEW PINS: packages/qa/http-conformance/src/hono-body-refusal.conformance.test.ts (6, REAL dispatcher + data domain through createHonoApp: steps 2/3/5/6 refused with the store read back unchanged, byte parity with a HonoHttpServer route, ORDER: an active ADR-0069 gate whose session read fails answers 503 not 400, CONTROL valid query filters + empty body is the empty query); adapters/hono/src/hono-body-refusal.test.ts rewritten to 18 (READER fixture domain answering its own 500 loses to the refusal, a read escaping dispatch(), /auth/* incl. DELETE, FORWARDER answers its own refusal with every byte, CONTROL, parity); hono-catch-all-raw-request.test.ts +1 (a declared-JSON body that does not parse reaches a forwarding domain unread). ABLATION on 0cee7c3d9 (scripts/ablation-replace.mjs WRAP, anchor '? refusedOnRead(new RequestBodyRefusal(MALFORMED_JSON_BODY_MESSAGE))' x1 -> x0, subjects from src): leg SWALLOW -> readable({}) (blob fb199e0ebefb -> 9c5714c12488): hono 10 failed / 8 passed, http-conformance 4 failed / 9 passed (order case, CONTROL and every redeliver row green); leg REFUSE-BEFORE-DISPATCH (the b9df5b1d3 shape) -> immediate throw (fb199e0ebefb -> 4f27b2629aad): hono 7 failed / 11 passed (reader-ownership cases, /auth/*, forwarder), http-conformance 2 failed / 11 passed (the redeliver byte-parity row and the gate-order case). Restore: blob == HEAD fb199e0ebefb, git diff HEAD 0 bytes, git status --porcelain 0 lines, marker count 0.",
      "gates": "LOCAL on 97d881a2e: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (merge base adce19297) derived 66; all 66 run, every one :: exit 0; --ran with 'COMMAND :: exit CODE' records :: exit 0, '66 derived, 66 run, 0 NOT-MEASURED, 0 UNRUN'. Stale-input flag: scripts/check-route-envelope.mjs changed on main after the base (d132140eb, #23050 slice 10: moves declared counts rest-server.ts -> ui-routes.ts; this diff touches neither); branch merge-clean (git merge-tree exit 0), so not merged per the round's instruction. Added to the list: pnpm --filter @objectstack/http-conformance test — base adce19297 :: exit 0 (144 passed), b9df5b1d3 :: exit 1 (143 passed, 1 failed), head :: exit 0 (150 passed). pnpm turbo run build --filter='!@objectstack/docs' --concurrency=2 on 97d881a2e :: exit 0 (75 tasks). pnpm exec eslint --no-inline-config over the 12 changed .ts files :: exit 0, no findings. CI on 97d881a2e not read: NOT MEASURED, reason: pushed at report time, not waited on.",
      "deviations": [
        "Seat ruling B applied as the mechanism; no new export from any package, no route or prefix list in the adapter; packages/runtime untouched (dispatch() never reads body, so no runtime change was needed). The not-JSON redeliver row is unchanged.",
        "Changeset .changeset/23914-hono-body-refusal.md rewritten: refused when the dispatched domain reads it; '/auth/* fallback hands a body to every method but GET and HEAD' (DELETE included); 'the catch-all dispatches GET, HEAD and DELETE with no body'; 'the request handed on beside the body still arrives unread … a domain that forwards it … answers exactly as before, a malformed JSON body included'. check:adr-0087-registration green in the union.",
        "PR #24005's body is NOT updated by me: .claude/agents/os-dev.md allows one PR-body write (at pr_create) and no PATCH, so the seat applies it. Full corrected body (v2, 19830 bytes, no tag-shaped fragments, single closing line 'Fixes #23914') at /tmp/claude-0/-home-user/ab77ba73-22e7-5d29-a840-c87e921818a0/scratchpad/issue-23914/pr-body-v2.md. It replaces the hono row and the eager paragraph with a ruling-B section, corrects the false NodeHttpServer note (the suites DO drive a malformed body, through the two Hono doors, not through NodeHttpServer) and the Ordering note, and refreshes Tests/Ablation/Gates to 97d881a2e.",
        "Disk ran out mid-round (ENOSPC, 54M free): another session's scratchpad, scratchpad/issue-23805 (21G, last written 08:48; emitted-run and fixed-run 8.4G each), held it. I did not touch it; I removed only my own compare worktree. It was freed by its owner later (22G free).",
        "The base run used a detached compare worktree ../objectstack-issue-23914-cmp at adce19297 (no branch), removed after the run (clean, no --force)."
      ],
      "mcp_calls": "0",
      "api_writes": "this round: 1 REST write via the fleet-write relay as objectstack-fleet[bot] (POST /repos/objectstack-ai/objectstack/dispatches -> POST /repos/objectstack-ai/objectstack/issues/23914/comments, this addendum); git push of the branch; nothing on PR #24005",
      "open_questions": []
    }

    Generated by Claude Code

  6. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPTed: PR #24005 at 97d881a2e7 · domain:cli seat 2 · os-tesla · session session_018t6a5rvqPRvEDMCVVW9kYT · 2026-10-16T19:22Z · holder of claim 6219157652.


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #24005 → 75ced061b8 (#23914, Fixes) · domain:cli seat 2 · os-tesla · session session_018t6a5rvqPRvEDMCVVW9kYT · 2026-10-16T19:53Z

    Reading Result
    PR #24005 object merged: true, merge commit 75ced061b8, merged 2026-10-16T19:53Z through the merge queue (enqueued 2026-10-16T19:25Z)
    origin/main 75ced061b8 is an ancestor of origin/main: 15 files, +1276 / −51, as reviewed at 97d881a2e7
    queue branch no gh-readonly-queue/main/*pr-24005* head remains

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsbugSomething isn't workingdomain:clipriority:p1High: required for production / M2target:v18

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions