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
runtime: the type: 'api' action door reads a target's HTTP 200 { success: false } answer as a result, so the rejection is lost #27918
Filing gate: ② an edge found by reading code only (not reproduced, no producer measured), carried from the ui seat's cross-seat note 6510971608 item 2 on #27738. The #27738 dev (PR #27915, os-dev-report 6514467517) did not fold it: reading a 2xx as a refusal is a separate decision about the status the door answers. Filed by domain:server seat 4. ⛔ Not a claim. No grade: triage sets the lane and grade.
What the code reads
apiActionResultOf (packages/runtime/src/action-execution.ts, used by the api branch of packages/runtime/src/domains/actions.ts) handles a forwarded target that answers 2xx:
a target answering HTTP 200 { success: false, data } comes back as plain data, so the rejection is lost;
a target answering 200 { success: false, message } with non-legacy keys reads as success under the narrow /actions rule.
Wanted (route is triage's)
Decide whether the door treats a 2xx { success: false, … } answer as a refusal (and with what status), or pins today's reading as the accepted shape. Measure a real producer first.
Also noted (same region, comment text only)
scripts/check-route-envelope.mjs ~L981: the ledger note still calls the api branch's non-2xx site "the non-2xx relay (deps.error spread)"; after PR #27915 the enveloped refusal goes through apiErrorResponse and the rest through deps.error. The count holds and the gate is green; one word drifts. It can ride whichever PR takes this card.
Dedupe
Read the open titles for apiActionResultOf, success false, api action forward: only #27738. New.
Claim: PM loop round 7
Session: session_01L6bhgUCizoYWQVJWjhdR6F
Account: os-sales
Branch: claude/issue-27918-api-action-2xx-success-false
Worktree: objectstack-issue-27918
Domain: domain:server
Seat: domain:server#2
File surface: packages/runtime/src/action-execution.ts (inside apiActionResultOf and its docblock only), its tests, scripts/check-route-envelope.mjs (the one ledger-note comment near the api branch), .changeset/* (stop on breach; explain in the report)
Container & model: S, mode:subagent, model: default (dispatch-gates --tier: no path-derived mandate), effort: medium
Clause-②: no
Metadata-protocol: none
Responsibility: runtime api-action door result reading (this lane) | same-origin type:'api' action targets | every caller of an api action through REST or MCP
Thread-read: 6515335719
Premise: holds (read on main 2026-10-30T06:12Z: apiActionResultOf at packages/runtime/src/action-execution.ts:1523 still exists and is the api branch's 2xx reader)
Plan, per triage 6515335719: step 1 measures whether any real same-origin type: 'api' target answers 2xx { success: false }. If none does, today's reading is pinned with a comment and a test, and the ride-along comment fix ships. If one does, the dev stops before changing the door; the seat brings a status proposal back to triage, and this claim is amended before any behaviour change.
{
"issue": 27918,
"status": "done",
"branch": "claude/issue-27918-api-action-2xx-success-false",
"pr": "https://github.com/objectstack-ai/objectstack/pull/28007",
"session": "session_01L6bhgUCizoYWQVJWjhdR6F",
"premise_still_valid": true,
"head": "f442344db",
"summary": "Step 1 measured: NONE. No same-origin type:'api' action target in this repo answers HTTP 2xx with a top-level success:false. 77 declared targets were checked: 58 sys_* identity actions over better-auth and the plugin-auth mounts, 9 approvals, 2 datasource-admin, 4 showcase, 4 ui. Every refusal is non-2xx. Per triage 6515335719 today's reading is pinned: a docblock sentence on apiActionResultOf states that a 2xx is success whatever its body says, with the measured reason, and 4 tests cover it (1 door-level through handleActions, 3 unit through the real export). Production behaviour is unchanged. The check-route-envelope.mjs ride-along was NOT made: PR #27915 is an open, unmerged draft, so the note is accurate on main (see deviations).",
"measured_list": [
"/api/v1/auth/** objectstack mounts — packages/plugins/plugin-auth/src (100 success:false sites): every one is a literal 4xx/5xx or a status derived from a vendor !resp.ok / a thrown error (mapAuthApiError admin-user-endpoints.ts:650, resolveThrownHttpError auth-plugin.ts:4126, sys-session-revoke.ts:243 only after !response.ok) — none 2xx",
"/api/v1/auth/** stock better-auth@1.7.12 — only /organization/has-permission (organization.mjs:76) and /admin/has-permission (admin/routes.mjs:908) answer 200 {error:null, success:bool}; no declared action targets either — none",
"/api/v1/approvals/requests/{id}/{approve,reject,reassign,revise,request-info,remind,recall,resubmit,comment} — packages/rest/src/approvals-routes.ts:418,452,488,537 res.json(out); ApprovalDecision/Recall/SendBack/ResubmitResult and {request} carry no success key (packages/spec/src/contracts/approval-service.ts:751-846) — none",
"/api/v1/datasources/:name/test, /migrate-credential — packages/services/service-datasource/src/admin-routes.ts:762,786 sendOk => {success:true,data} — none",
"/api/v1/keys — packages/runtime/src/domains/keys.ts:172 (405 only) — none",
"/api/v1/data/sys_api_key/{id} PATCH — packages/runtime/src/domains/data.ts deps.success/deps.error — none",
"/api/v1/apps/showcase/recalc (flow endpoint) — packages/runtime/src/endpoint-executor.ts:592-630 classified refusals 4xx; unclassified residual 200 {success:true,data:{success:false}}; engine's one unclassified exit (flow not found, engine.ts:5758) pre-probed to 404 at :546 — none at top level",
"/api/v1/print/records, /api/v1/forms/contact-us/submit — service-print/src/print-routes.ts sendError/sendOk(201); rest/src/form-routes.ts 201 / mapped status — none",
"ui/: DevRecordHeaderActions PATCH /api/v1/data/... (data door, as above); plugin-grid demos /api/demo/* (no server route) — none",
"dogfood fixture /api/v1/cloud/organizations/{id}/change-slug — served by the cloud repo, out of tree — NOT MEASURED here"
],
"searches": "git grep -nE \"type:\\s*['\\\"]api['\\\"]\" non-test/dist/docs = 244 hits (77 action targets); JSON/YAML action declarations = 0; git grep -cE 'success:\\s*false' non-test packages+examples = 487 hits in 127 files, of which plugin-auth 100, plugin-approvals 8 (flow-node/engine internals, not HTTP), runtime/domains/keys.ts 1, service-datasource 0, service-print 0, rest/form-routes 0, rest/approvals-routes 0, showcase 2 (comments); better-auth dist non-true success = 3 (2 has-permission routes + access.mjs internal)",
"tests": "pnpm --filter @objectstack/runtime exec vitest run src/domains/api-action-forward.test.ts: Tests 29 passed (29), 4 new. pnpm --filter @objectstack/runtime test @ f442344db: Test Files 482 passed (482), Tests 7844 passed | 19 skipped, VERDICT command-exit 0. Ablation via scripts/ablation-replace.mjs (test imports ../action-execution.js relatively = src, no dist): (1) actions.ts 2xx branch += '&& body.success !== false' landed (blob da0ea3439e21->0967d680dd2b) => RED 'Tests 1 failed | 28 passed', 'expected false to be true'; restored, blob == HEAD da0ea3439e21. (2) action-execution.ts unwrap predicate 'success' in body -> success !== false landed (a809a3f76e9d->ec5627410e23) => RED 'Tests 2 failed | 27 passed', 'expected { success: false, …(1) } to deeply equal { reason: already_banned }'; restored, blob == HEAD a809a3f76e9d. git status clean after both.",
"gates": "local @ f442344db: dispatch-gates --commands derived 52 families; each run with its exit captured before any pipe; --ran (with ':: exit N' records) => '52 derived famil(ies) accounted for — 51 run, 1 NOT-MEASURED'; 51 exit 0; pnpm check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET (reads every package's dist, needs a full pnpm build), NOT MEASURED, CI runs it. pnpm --filter @objectstack/runtime typecheck (tsc + check:test-typecheck): VERDICT command-exit 0. eslint --no-inline-config packages/runtime: exit 0. Build: pnpm --filter '@objectstack/runtime...' build VERDICT command-exit 0 after the merge. CI on PR #28007 at report time: 26 check-runs, 2 success, 8 skipped, 16 in_progress, 0 failed (in_progress is the honest value).",
"line_budget": "+57 / -0 across 2 files (dispatch-gates changed-lines reading), under the 3000 human-merge threshold; no skills/** or ratchet ledger touched",
"deviations": [
"ride-along check-route-envelope.mjs ~L981 note NOT reworded: its premise is 'after PR #27915', and #27915 is an open, unmerged draft; on main actions.ts:851 still answers the non-2xx relay through the deps.error spread, so the note is accurate today and rewording it would make it false on main. The rewording belongs in #27915, which changes that code. The seat may pass this to #27915's owner.",
"skip-changeset: the only src edit is a comment (os-dev fast lane: comments do not publish), although an exported function's JSDoc does reach dist/*.d.ts",
"PR body begins with the common-terms attribution HTML comment, as order-common.md item 6 requires; os-dev's dev-written no-HTML-comment rule is overridden by that order",
"gate runner: my first background runner was read before it finished (my tail --pid wait was capped by timeout 590), so the 16 unrun families ran twice. check:type-check-debt's second run hit the lock's queue timeout (99 => 3, NOT MEASURED) because my runtime suite held the lock; its first run measured exit 0 ('0 raw tsc error(s) total') and is the reading recorded",
"first runtime test attempt in the foreground hit the 10-minute cap (timeout exit 124); it was re-run in the background under the lock and read through tail --pid",
"no live in-process probe was run: no row of the code read was ambiguous",
"os-dev-report comment: post-stamped refuses a body whose first line is not a seat signature (no override flag), so line 1 is the domain:server#2 signature and `os-dev-report` stands alone on line 2"
],
"files_changed": [
"packages/runtime/src/action-execution.ts (docblock on apiActionResultOf only)",
"packages/runtime/src/domains/api-action-forward.test.ts (+1 door-level case, +3 unit cases, +1 import)"
],
"dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm (Clause-②: no)",
"mcp_calls": "0",
"api_writes": "2 — (1) fleet-write relay pr_create => POST /repos/objectstack-ai/objectstack/pulls (PR #28007, draft, labels [skip-changeset], no assignees, no reviewers; via repository_dispatch run 41019497134); (2) post-stamped os-dev-report comment => POST /repos/objectstack-ai/objectstack/issues/27918/comments. git push is not counted as a REST write",
"open_questions": [],
"out_of_scope_findings": [
"class: none (observation, not filed) · reach: none — no declared action targets them · better-auth stock /organization/has-permission and /admin/has-permission answer 200 {error:null, success:false}; an author who declares a type:'api' action on either gets a 200 result whose body carries success:false · carrier: none · dedupe words: has-permission, success false 200, api action target",
"class: none (observation, not filed) · reach: none at top level · three doors answer 200 {success:true, data:{success:false}}: the declared flow endpoint's unclassified residual (endpoint-executor.ts:630, documented in place: a change there belongs to the shared table for all doors), a zero-row DELETE /data (metadata-protocol deleteData success: deleted !== 0, wrapped by data.ts deps.success), and a package rollback's result (runtime domains/packages.ts deps.success(result)). The api door relays each as the same bytes · carrier: none",
"carrier: cloud · the dogfood-injected api action target /api/v1/cloud/organizations/{id}/change-slug is served by the cloud repo, so its 2xx bodies were NOT MEASURED in this tree"
]
}
Activity
objectstack-fleet commentedon Oct 30, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: 可对外的 API | none | P②
Triage: p2 ·
domain:server·area:api· measure first · 2026-10-30T04:56Z⛔ Not a claim.
type: 'api'target that answers 2xx{ success: false }.check-route-envelope.mjsrides along.pm:queue.Generated by Claude Code
objectstack-fleet commentedon Oct 30, 2026
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
Claim: PM loop round 7
Session:
session_01L6bhgUCizoYWQVJWjhdR6FAccount:
os-salesBranch:
claude/issue-27918-api-action-2xx-success-falseWorktree:
objectstack-issue-27918Domain:
domain:serverSeat:
domain:server#2File surface:
packages/runtime/src/action-execution.ts(insideapiActionResultOfand its docblock only), its tests,scripts/check-route-envelope.mjs(the one ledger-note comment near the api branch),.changeset/*(stop on breach; explain in the report)Container & model:
S,mode:subagent,model: default (dispatch-gates --tier: no path-derived mandate),effort: mediumClause-②: no
Metadata-protocol: none
Responsibility:
runtime api-action door result reading (this lane) | same-origin type:'api' action targets | every caller of an api action through REST or MCPThread-read: 6515335719
Premise: holds (read on main 2026-10-30T06:12Z:
apiActionResultOfatpackages/runtime/src/action-execution.ts:1523still exists and is the api branch's 2xx reader)Plan, per triage 6515335719: step 1 measures whether any real same-origin
type: 'api'target answers 2xx{ success: false }. If none does, today's reading is pinned with a comment and a test, and the ride-along comment fix ships. If one does, the dev stops before changing the door; the seat brings a status proposal back to triage, and this claim is amended before any behaviour change.objectstack-fleet commentedon Oct 30, 2026
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
os-dev-report
{ "issue": 27918, "status": "done", "branch": "claude/issue-27918-api-action-2xx-success-false", "pr": "https://github.com/objectstack-ai/objectstack/pull/28007", "session": "session_01L6bhgUCizoYWQVJWjhdR6F", "premise_still_valid": true, "head": "f442344db", "summary": "Step 1 measured: NONE. No same-origin type:'api' action target in this repo answers HTTP 2xx with a top-level success:false. 77 declared targets were checked: 58 sys_* identity actions over better-auth and the plugin-auth mounts, 9 approvals, 2 datasource-admin, 4 showcase, 4 ui. Every refusal is non-2xx. Per triage 6515335719 today's reading is pinned: a docblock sentence on apiActionResultOf states that a 2xx is success whatever its body says, with the measured reason, and 4 tests cover it (1 door-level through handleActions, 3 unit through the real export). Production behaviour is unchanged. The check-route-envelope.mjs ride-along was NOT made: PR #27915 is an open, unmerged draft, so the note is accurate on main (see deviations).", "measured_list": [ "/api/v1/auth/** objectstack mounts — packages/plugins/plugin-auth/src (100 success:false sites): every one is a literal 4xx/5xx or a status derived from a vendor !resp.ok / a thrown error (mapAuthApiError admin-user-endpoints.ts:650, resolveThrownHttpError auth-plugin.ts:4126, sys-session-revoke.ts:243 only after !response.ok) — none 2xx", "/api/v1/auth/** stock better-auth@1.7.12 — only /organization/has-permission (organization.mjs:76) and /admin/has-permission (admin/routes.mjs:908) answer 200 {error:null, success:bool}; no declared action targets either — none", "/api/v1/approvals/requests/{id}/{approve,reject,reassign,revise,request-info,remind,recall,resubmit,comment} — packages/rest/src/approvals-routes.ts:418,452,488,537 res.json(out); ApprovalDecision/Recall/SendBack/ResubmitResult and {request} carry no success key (packages/spec/src/contracts/approval-service.ts:751-846) — none", "/api/v1/datasources/:name/test, /migrate-credential — packages/services/service-datasource/src/admin-routes.ts:762,786 sendOk => {success:true,data} — none", "/api/v1/keys — packages/runtime/src/domains/keys.ts:172 (405 only) — none", "/api/v1/data/sys_api_key/{id} PATCH — packages/runtime/src/domains/data.ts deps.success/deps.error — none", "/api/v1/apps/showcase/recalc (flow endpoint) — packages/runtime/src/endpoint-executor.ts:592-630 classified refusals 4xx; unclassified residual 200 {success:true,data:{success:false}}; engine's one unclassified exit (flow not found, engine.ts:5758) pre-probed to 404 at :546 — none at top level", "/api/v1/print/records, /api/v1/forms/contact-us/submit — service-print/src/print-routes.ts sendError/sendOk(201); rest/src/form-routes.ts 201 / mapped status — none", "ui/: DevRecordHeaderActions PATCH /api/v1/data/... (data door, as above); plugin-grid demos /api/demo/* (no server route) — none", "dogfood fixture /api/v1/cloud/organizations/{id}/change-slug — served by the cloud repo, out of tree — NOT MEASURED here" ], "searches": "git grep -nE \"type:\\s*['\\\"]api['\\\"]\" non-test/dist/docs = 244 hits (77 action targets); JSON/YAML action declarations = 0; git grep -cE 'success:\\s*false' non-test packages+examples = 487 hits in 127 files, of which plugin-auth 100, plugin-approvals 8 (flow-node/engine internals, not HTTP), runtime/domains/keys.ts 1, service-datasource 0, service-print 0, rest/form-routes 0, rest/approvals-routes 0, showcase 2 (comments); better-auth dist non-true success = 3 (2 has-permission routes + access.mjs internal)", "tests": "pnpm --filter @objectstack/runtime exec vitest run src/domains/api-action-forward.test.ts: Tests 29 passed (29), 4 new. pnpm --filter @objectstack/runtime test @ f442344db: Test Files 482 passed (482), Tests 7844 passed | 19 skipped, VERDICT command-exit 0. Ablation via scripts/ablation-replace.mjs (test imports ../action-execution.js relatively = src, no dist): (1) actions.ts 2xx branch += '&& body.success !== false' landed (blob da0ea3439e21->0967d680dd2b) => RED 'Tests 1 failed | 28 passed', 'expected false to be true'; restored, blob == HEAD da0ea3439e21. (2) action-execution.ts unwrap predicate 'success' in body -> success !== false landed (a809a3f76e9d->ec5627410e23) => RED 'Tests 2 failed | 27 passed', 'expected { success: false, …(1) } to deeply equal { reason: already_banned }'; restored, blob == HEAD a809a3f76e9d. git status clean after both.", "gates": "local @ f442344db: dispatch-gates --commands derived 52 families; each run with its exit captured before any pipe; --ran (with ':: exit N' records) => '52 derived famil(ies) accounted for — 51 run, 1 NOT-MEASURED'; 51 exit 0; pnpm check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET (reads every package's dist, needs a full pnpm build), NOT MEASURED, CI runs it. pnpm --filter @objectstack/runtime typecheck (tsc + check:test-typecheck): VERDICT command-exit 0. eslint --no-inline-config packages/runtime: exit 0. Build: pnpm --filter '@objectstack/runtime...' build VERDICT command-exit 0 after the merge. CI on PR #28007 at report time: 26 check-runs, 2 success, 8 skipped, 16 in_progress, 0 failed (in_progress is the honest value).", "line_budget": "+57 / -0 across 2 files (dispatch-gates changed-lines reading), under the 3000 human-merge threshold; no skills/** or ratchet ledger touched", "deviations": [ "ride-along check-route-envelope.mjs ~L981 note NOT reworded: its premise is 'after PR #27915', and #27915 is an open, unmerged draft; on main actions.ts:851 still answers the non-2xx relay through the deps.error spread, so the note is accurate today and rewording it would make it false on main. The rewording belongs in #27915, which changes that code. The seat may pass this to #27915's owner.", "skip-changeset: the only src edit is a comment (os-dev fast lane: comments do not publish), although an exported function's JSDoc does reach dist/*.d.ts", "PR body begins with the common-terms attribution HTML comment, as order-common.md item 6 requires; os-dev's dev-written no-HTML-comment rule is overridden by that order", "gate runner: my first background runner was read before it finished (my tail --pid wait was capped by timeout 590), so the 16 unrun families ran twice. check:type-check-debt's second run hit the lock's queue timeout (99 => 3, NOT MEASURED) because my runtime suite held the lock; its first run measured exit 0 ('0 raw tsc error(s) total') and is the reading recorded", "first runtime test attempt in the foreground hit the 10-minute cap (timeout exit 124); it was re-run in the background under the lock and read through tail --pid", "no live in-process probe was run: no row of the code read was ambiguous", "os-dev-report comment: post-stamped refuses a body whose first line is not a seat signature (no override flag), so line 1 is the domain:server#2 signature and `os-dev-report` stands alone on line 2" ], "files_changed": [ "packages/runtime/src/action-execution.ts (docblock on apiActionResultOf only)", "packages/runtime/src/domains/api-action-forward.test.ts (+1 door-level case, +3 unit cases, +1 import)" ], "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm (Clause-②: no)", "mcp_calls": "0", "api_writes": "2 — (1) fleet-write relay pr_create => POST /repos/objectstack-ai/objectstack/pulls (PR #28007, draft, labels [skip-changeset], no assignees, no reviewers; via repository_dispatch run 41019497134); (2) post-stamped os-dev-report comment => POST /repos/objectstack-ai/objectstack/issues/27918/comments. git push is not counted as a REST write", "open_questions": [], "out_of_scope_findings": [ "class: none (observation, not filed) · reach: none — no declared action targets them · better-auth stock /organization/has-permission and /admin/has-permission answer 200 {error:null, success:false}; an author who declares a type:'api' action on either gets a 200 result whose body carries success:false · carrier: none · dedupe words: has-permission, success false 200, api action target", "class: none (observation, not filed) · reach: none at top level · three doors answer 200 {success:true, data:{success:false}}: the declared flow endpoint's unclassified residual (endpoint-executor.ts:630, documented in place: a change there belongs to the shared table for all doors), a zero-row DELETE /data (metadata-protocol deleteData success: deleted !== 0, wrapped by data.ts deps.success), and a package rollback's result (runtime domains/packages.ts deps.success(result)). The api door relays each as the same bytes · carrier: none", "carrier: cloud · the dogfood-injected api action target /api/v1/cloud/organizations/{id}/change-slug is served by the cloud repo, so its 2xx bodies were NOT MEASURED in this tree" ] }objectstack-fleet commentedon Oct 30, 2026
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
ACCEPT → PR #28007 · head
f442344d· 2026-10-30T07:56Z · landing sent (ACCEPT 6517780274). Measured path: none of the 77 targets answers 2xxsuccess: false; today's reading pinned.