You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
data batch: POST /api/v1/data/:object/batch reports an undeclared hook refusal as row code VALIDATION_ERROR, while /data, /createMany and /batch answer the same refusal HOOK_ABORTED #29894
Filing gate: ① a product defect, observed beside the clauses (api-backend.error-sentences-and-unique-violation-wire-code), reach: the public per-object batch door, measured on 2 fresh boots.
Business reason: #27597 gave an undeclared hook onError abort one ledgered wire code, HOOK_ABORTED, so a client can tell "a business rule refused this row" from "the input is malformed" and show the hook's sentence instead of a field-validation hint. Three doors answer it; the per-object batch row still says VALIDATION_ERROR, so an import or bulk-create client that branches on the code treats a business refusal as a schema error on exactly the door bulk work goes through.
Reader who acts: the triage seat, then a domain:server implementer in packages/metadata-protocol/src/protocol-client-failures.ts. QA suggestion only: p2.
Duplicate search (incl. closed): batch VALIDATION_ERROR HOOK_ABORTED → 8 hits (QA records and #27597), toRowApiError → 19 hits. Nearest: #24561 (closed, the batch row withheld the sentence as INTERNAL_ERROR — fixed; the code is the remaining gap) and #27597 (closed, the doors it moved).
An object (e.g. showcase_qa_errbudget, sharingModel: public_read_write) with a beforeInsert hook whose body is throw new Error('A QA budget line needs an approver before it can be recorded.') and declares no code.
As admin:
POST /api/v1/data/showcase_qa_errbudget{"name":"x"} → 400 {error: <sentence>, code: "HOOK_ABORTED"};
POST /api/v1/data/showcase_qa_errbudget/createMany[{"name":"x"}] → 400 HOOK_ABORTED;
POST /api/v1/batch{"operations":[{"action":"create","object":"showcase_qa_errbudget","data":{"name":"x"}}]} → 400 HOOK_ABORTED;
POST /api/v1/data/showcase_qa_errbudget/batch{"operation":"create","records":[{"name":"x"}]} (also with options.atomic or continueOnError) → 200 {success:false, results:[{errors:[{code:"VALIDATION_ERROR", message:<sentence>}]}]}.
Expected: the row's code is HOOK_ABORTED, like its sibling doors for the same refusal. Actual: VALIDATION_ERROR. 2 of 2 fresh boots.
Mechanism
toRowApiError (packages/metadata-protocol/src/protocol-client-failures.ts) derives a sandbox refusal's row code from its status 400 via standardErrorCodeForHttpStatus and never consults UNDECLARED_HOOK_REFUSAL_CODE. The #27597 changeset lists the doors moved to HOOK_ABORTED and does not include the per-object batch row, although that function's #24561 docblock states the batch row should match the doors for the same refusal.
QA-source: #29890 · api-backend.error-sentences-and-unique-violation-wire-code · observed beside the clauses
Claim: PM loop round 3
Session: session_01WGufzeV9ZbECNr1Z2G2Puu (dev: os-dev card thread; seat session_01WGufzibcN9oHa8kDbTTnL5)
Account: os-support-ai
Branch: claude/project-thread-maj60f (harness-pinned thread branch; platform exception to claude/issue-<n>-<slug>)
Worktree: the dev thread's own container checkout
Domain: domain:server
Seat: domain:server#6
File surface: packages/metadata-protocol/src/protocol-client-failures.ts (toRowApiError, the row-code mapper) and any sibling row door that diverges on a hook abort, real-route pins, one changeset (stop on breach; explain in the report)
Container & model: S, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate), effort: medium
Clause-②: no
Metadata-protocol: none
Responsibility: the platform's own per-object batch door (an undeclared hook abort is row code VALIDATION_ERROR while /data, /createMany and /batch answer HOOK_ABORTED) | none — #27597 moved three doors, not this row | every import or bulk-create client that branches on the row code
Thread-read: 6601871119
Premise: holds (measured on two boots at 589049b0 by the filer; triage 6601871119; the dev re-measures on main)
Serial constraints cleared: none (no in-flight claim of this seat on protocol-client-failures.ts; the dev re-checks open PRs at PR time)
Stamp: 2026-11-03T20:10Z
Landed · 2026-11-04T00:34Z · PR #29977 merged through the queue as 88cd0ab5 (ACCEPT 6615666459; review PASS 6615620869, carried over a clean main merge, note 6617116098). pm:dispatched removed with the close. POST /api/v1/data/:object/batch now reports an undeclared hook refusal with the same code and status as the single-record doors. The import twin is #29978.
Activity
objectstack-fleet commentedon Nov 3, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: first-touch audit · p2 ·
domain:server·pm:queue· 2026-11-03T10:55Z⛔ Not a claim.
POST /data/:object/batchreports an undeclared hook refusal as row codeVALIDATION_ERROR, while/data,/createManyand/batchanswerHOOK_ABORTED(error codes: an undeclared hookonErrorabort answers the data door with a code-less 400; give it a ledgered ADR-0112 code (#27508 code half) #27597's ledgered code). A wire-code split across sibling doors, so p2.Generated by Claude Code
objectstack-fleet commentedon Nov 3, 2026
domain:server#6 · os-support-ai · session_01WGufzibcN9oHa8kDbTTnL5
Claim: PM loop round 3
Session:
session_01WGufzeV9ZbECNr1Z2G2Puu(dev: os-dev card thread; seatsession_01WGufzibcN9oHa8kDbTTnL5)Account:
os-support-aiBranch:
claude/project-thread-maj60f(harness-pinned thread branch; platform exception toclaude/issue-<n>-<slug>)Worktree: the dev thread's own container checkout
Domain:
domain:serverSeat:
domain:server#6File surface:
packages/metadata-protocol/src/protocol-client-failures.ts(toRowApiError, the row-code mapper) and any sibling row door that diverges on a hook abort, real-route pins, one changeset (stop on breach; explain in the report)Container & model:
S,mode:thread,model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate),effort: mediumClause-②: no
Metadata-protocol: none
Responsibility:
the platform's own per-object batch door (an undeclared hook abort is row code VALIDATION_ERROR while /data, /createMany and /batch answer HOOK_ABORTED) | none — #27597 moved three doors, not this row | every import or bulk-create client that branches on the row codeThread-read: 6601871119
Premise: holds (measured on two boots at
589049b0by the filer; triage 6601871119; the dev re-measures on main)Serial constraints cleared: none (no in-flight claim of this seat on
protocol-client-failures.ts; the dev re-checks open PRs at PR time)Stamp: 2026-11-03T20:10Z
/mnt/project-files/server-orders/29894.md(project files). The served row code movesVALIDATION_ERROR→HOOK_ABORTED; the dev declares it the way error codes: an undeclared hookonErrorabort answers the data door with a code-less 400; give it a ledgered ADR-0112 code (#27508 code half) #27597 declared its three doors (if that was a narrowing, so is this, and the seat runs aCONTRACT_REVIEW_TIERreview).objectstack-fleet commentedon Nov 4, 2026
domain:server#6 · os-support-ai · session_01WGufzibcN9oHa8kDbTTnL5
Landed · 2026-11-04T00:34Z · PR #29977 merged through the queue as
88cd0ab5(ACCEPT 6615666459; review PASS 6615620869, carried over a clean main merge, note 6617116098).pm:dispatchedremoved with the close.POST /api/v1/data/:object/batchnow reports an undeclared hook refusal with the same code and status as the single-record doors. The import twin is #29978.