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
[finding] The two error-handling examples on content/docs/api/client-sdk.mdx read error.code off an untyped catch binding — they do not compile in a reader's own strict project #12342
Measured while landing #12048 (PR #12339), which gives the SDK docs pages a resolution environment that can see @objectstack/client. That change is what makes this measurable at all — before it, every fence on this page red with TS2307 and nothing downstream of the import was ever reached.
⛔ Not a marker/coverage question, and deliberately not filed as part of #11942's remaining scope in that sense: this is about what the examples teach, and it is true whether or not the blocks are ever marked.
The defect
Two blocks on the page — "Error Handling" (around line 582) and "Per-field validation errors" (around line 609) — are written:
Under strict (which implies useUnknownInCatchVariables), error is unknown, so every one of those reads is TS18046: 'error' is of type 'unknown'. A reader who copies this into their own strict project — the default for tsc --init since TS 4.4, and what this repo's own packages use — does not get working code; they get a compile error, with no hint on the page that narrowing is required.
Measurement
With all 13 fences on the page temporarily marked and the gate run against the post-#12048 surface (tree reverted afterwards), these two blocks produced 10 of the 114 diagnostics:
block
diagnostics
582 Error Handling
6 — 1× TS2304 client, 5× TS18046 'error' is of type 'unknown'
The TS2304s are the page's deliberate continuation-fragment convention (Quick Start establishes client once) and are not this card. The TS18046s are not — they would fire in a reader's file too, where client is defined.
Why it is worth fixing rather than accepting
This is the page a customer opens to learn the SDK, and error handling is the part they copy most literally. The fix is also what the page should teach anyway: the SDK throws a typed error, and the example should show the reader how to reach it — a narrowing helper or type guard exported from @objectstack/client, or at minimum an instanceof / shape check in the example. Whether such a guard exists and is exported is the first thing to check; if it does not, the honest reading is that the SDK has no supported way to narrow a caught error, which is a bigger finding than the docs page.
Once the examples narrow honestly they become self-contained enough to carry an os:check marker (the remaining TS2304 client is the page's separate continuation convention), so this also converts two more fences from unverifiable to gated.
showFieldError in the second block — an illustrative UI callback, correct as prose.
Refs: #12048 / PR #12339 (the surface fix that made this measurable) · #11942 (the page's coverage card) · #8140 (the narrowing that falsified two other examples on this page)
Declared file surface: the two error-handling examples on content/docs/api/client-sdk.mdx. ⛔ Not the whole page.
Serial:⚠️#11942 is pm:blocked on this same file — it is about that page's 13 TypeScript fences carrying 0os:check markers, so none of them is ever compiled. No open PR claims the file, so it is free, but see Zone 1.2.
Zone 1 — rulings (scope, not facts; not renegotiable)
The surface is the two examples the card names. ⛔ Do not sweep the page's other fences.
Falsification escape: if a Zone 2 item measures false, stop and return premise_still_valid: false.
Zone 2 — my assumptions; falsify them before building on them
a. I take the card's reading that both examples read error.code off an untyped catch binding and so do not compile under a reader's useUnknownInCatchVariables. ⚠️ I have not checked the repo's own tsconfig setting for it — the claim is about a reader's strict project, not this repo's, so do not "verify" it against local settings and call it settled.
b. ⚠️ I assume error.code is the right shape to narrow to. Read what the client SDK actually throws before you write the narrowing — a corrected example that compiles but names a property the SDK never sets is worse than the current one, which at least fails loudly.
c. I have not counted whether only 2 examples on the page have this shape. If there are more, report the count; fix the 2 the card names and say what you left.
Zone 3 — suggested route (not binding)
Read what the SDK throws → write the narrowing that is both compilable and true → keep the examples short enough to stay readable. If a shared error type is exported for exactly this, prefer it over a hand-written interface.
Gate union at the final commit; re-derive on STALE TREE. Run the docs link checker if it is in the union. Exit codes captured before any pipe.
{
"issue": 12342,
"status": "done",
"branch": "claude/issue-12342-client-sdk-catch-binding",
"pr": "https://github.com/objectstack-ai/objectstack/pull/12387",
"premise_still_valid": true,
"summary": "The card's defect is real and reproduced exactly: the two Error Handling examples on content/docs/api/client-sdk.mdx produce 5 and 2 TS18046 respectively under a strict profile copied from check:skill-examples' own tsconfig. Both blocks now narrow through an `isApiError` guard the first block declares, in the same continuation style the page already uses for `client`; block 1 is otherwise self-contained. No os:check markers were added (Zone 1.2), so the corrected examples remain unverified by CI by construction — the PR body says this in its own section rather than letting the green checks imply otherwise. ZONE 2 RESULTS: (a) CONFIRMED by measurement, not by this repo's tsconfig — a standalone harness with strict:true, target ES2020, types:[] reproduces the card's per-block counts exactly. (b) PARTLY FALSIFIED, and this changed the fix: `error.code` IS a property the SDK sets, but the triage's suggested narrowing target `StandardError` is the WRONG shape on three measured counts — its `code` is the closed StandardErrorCode enum which does NOT contain `VALIDATION_FAILED` (that code is registered in ERROR_CODE_LEDGER, and it is the exact value both examples branch on, so typing code as StandardErrorCode would turn the page's own comparison into a compile error); it declares no `fields` member, which is the entire subject of block 2; and it requires category/retryable which the flat REST envelope never carries. StandardError describes the server's error ENVELOPE, not the thrown object. Your charter boundary equated 'not reliably StandardError-shaped' with 'no supported way to narrow' — those measured DIFFERENT: the thrown value is always a real Error and always carries httpStatus (error.httpStatus = res.status, unconditional at the !res.ok seam), and carries it on none of the pre-response throws, so httpStatus is an honest, teachable discriminator. So the operative stop condition was false and I proceeded; no new export was added. (c) COUNTED: THREE blocks on the page have the shape, not two. Fixed the two the card names; left the automation.execute block inside the API-surface tour fence and filed it. ALSO TOUCHED, both declared in the PR body: the page's MDX header comment, which carried a now-false sentence claiming these blocks could only be made to compile by 'teaching worse code than the page teaches now'; and one prose sentence claiming error.code 'is always' a string, which is measurably not always (it is undefined when the server sent neither spelling). DISCLOSURES: (1) I pushed the empty probe branch first as required, then rebased onto a fresher origin/main after dispatch-gates reported STALE TREE, which required one `git push --force-with-lease` on my own branch before any PR existed. No other work could have been on that ref. Next time the probe should be followed by a merge rather than a rebase. (2) The REST channel is unreachable from this seat — every api.github.com call returns 403 'GitHub access is not enabled for this session', which is the condition already filed as #12293. The skip-changeset label therefore went through the declared read-union-write fallback (read: ['size/s'] with the size labeler already settled, wrote ['size/s','skip-changeset']), and read-back confirms both are present. Dedup for the two new findings used MCP list_issues over 60 recent open issues plus local grep, because MCP search_issues returned total_count 0 even for the known-open #12342 — its index is not answering, so a 0 from it was not treated as evidence.",
"tests": "All 23 gate families derived by `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (clean derivation, no STALE TREE) run at final commit d87b4d5ecf — every exit code captured into a variable before any pipe, never read off a tail. All 23 EXIT=0: check:cross-package-test-inputs, check:doc-anchors, check:doc-authoring, lint/check:doc-formula-expressions, lint/check:doc-security-posture, spec/check:docs, check:docs-audit-scope, check:docs-redirects, check:docs-single-h1, spec/check:empty-state, spec/check:liveness, check:published-readme-links, check:react-page-adapter-contract, check:role-word, spec/check:skill-examples, spec/check:strictness-ledger, spec/check:variant-docs, check-ci-filter-parity.mjs, check-cross-package-test-inputs.mjs, check-doc-frontmatter.mjs, check-doc-route-spelling.mjs, check-docs-section-name.mjs, check-section-landing-index.mjs. check:skill-examples prints its own verdict line: '260 prose examples type-check across 3 surface(s) — every marked block parsed, so tsc ran the SEMANTIC pass on all of them'. Two of these were red on first run with PREREQUISITE NOT MET (unbuilt @objectstack/formula and @objectstack/lint) — nothing measured, not findings; a build of formula/lint/client/client-react/spec through `bash scripts/pm/os-verify-lock.sh -c ...` (VERDICT command-exit 0, held 178s, waited 2s, 33/33 tasks) cleared them and both then passed. THE CARD'S OWN MEASUREMENT, reproduced in a standalone tsc program using the compilerOptions check-skill-examples writes for its build dir (strict:true, target ES2020, module ESNext, moduleResolution bundler, lib ES2020+DOM+DOM.Iterable, types:[]), tsc 6.0.3, with `client` and `showFieldError` declared so the page's separate TS2304 continuation convention is excluded: BEFORE, exit 2 with exactly 7 diagnostics — before_block1.ts lines 4-8 all 'error TS18046: error is of type unknown' (5), before_block2.ts(4,7) and (5,21) the same (2). That is the card's 5+2 to the line. AFTER, the two blocks EXTRACTED PROGRAMMATICALLY FROM THE COMMITTED FILE (not from a draft) and compiled in the same program: exit 0, zero diagnostics. Block 1 also compiles alone against nothing but a declared `client`, confirming its guard is self-contained. NO ABLATION was run — there is no gate to prove failable here, which is precisely the Zone 1.2 tension: no marker means no CI program compiles these blocks at all. REPO-WIDE `pnpm lint` NOT RUN, and the narrowing is proven rather than asserted: asked through eslint's own config resolution (new ESLint({allowInlineConfig:false}) in this tree), `isPathIgnored('content/docs/api/client-sdk.mdx')` returns true and lintFiles reports 'File ignored because no matching configuration was supplied.' So (1) the population comes from eslint's config, not my guess; (2) the lintable-file count in this diff is 0, read from the lintFiles result; (3) no config applies to the path at all, so no type-aware linting exists through which this diff could move an untouched file's verdict. No changeset: `content/` is on changeset-check's own 'releases nothing' list in .github/workflows/pr-automation.yml, and two recent docs-only merges (e3430a2cb5, b1581b044d) carry none. Control-byte scan on the edited file clean (grep -naP over the C0 set plus DEL: no match).",
"open_questions": [
{
"question": "`@objectstack/client` exports a `StandardError` interface that does not describe the object the client throws — its `code` is the closed StandardErrorCode enum (missing the ledger-registered VALIDATION_FAILED the SDK itself normalizes to), it declares no `fields`, and it requires category/retryable which the flat REST envelope never sets. Every consumer narrowing a caught SDK error must therefore hand-write the shape, as the docs example now does. Should the SDK own this?",
"options": [
"A — leave it: the docs now teach the guard explicitly, no public surface grows, and consumers keep full freedom over how much of the shape they depend on.",
"B — export a `ThrownApiError` type plus an `isApiError` guard from @objectstack/client, and have the docs import them instead of declaring them inline.",
"C — fix `StandardError` itself so it describes the thrown object (add fields, widen code, make category/retryable optional).",
"D — retire or rename `StandardError` to say what it is (the server envelope), independent of whether a thrown-error type is added."
],
"recommendation": "A for now, with D worth its own card. Real business need: nothing measured pulls for B — no in-repo consumer imports StandardError to narrow a catch, so B would be a speculative public-surface expansion, and the dispatch charter explicitly declined it as a rider. Long-term soundness: the guard belongs at the producer eventually, but adding a public export whose only consumer today is a docs snippet is the workaround shape, not the contract-first one; D is the contract-first move, because a type whose NAME says 'error' and whose CONTENT is 'wire envelope' is exactly what made this card cost a measurement. Hard-to-get-wrong for AI-written code: A is neutral and D is a strict improvement — the current name invites precisely the wrong narrowing, and I nearly wrote it before checking the enum. Startup scope discipline: B and C both widen or reshape a published surface for zero measured pull; D is a naming/precision fix with no new capability. So: ship A here, file D for triage if the maintainer agrees the name is the defect."
}
],
"out_of_scope_findings": [
"filed as #12388: a THIRD example on the same page (the automation.execute block inside the API-surface tour fence) reads err.httpStatus/err.code/err.details off an untyped catch binding — 4 more would-be TS18046, left alone because it is a fragment inside a ~150-line multi-section fence and narrowing it is a shaping decision, not a mechanical edit",
"filed as #12389: the page's Error Codes table lists nine StandardErrorCode members and omits VALIDATION_FAILED, the ledger-registered code its own two examples branch on — a reader working from the table writes VALIDATION_ERROR and never matches; the table also nowhere says the vocabulary is StandardErrorCode union the serving side's ledger"
]
}
(Re-post of the report in the comment directly above. That one led with the <!-- os-dev-report --> HTML-comment marker the contract asks for, and GitHub's
body sanitizer ate it — read-back shows the stored body starting at the
seat (the REST API answers 403 `GitHub access is not enabled for this session`,the condition filed as #12293, and the MCP surface exposes no update-commenttool), so the marker is restored here as literal leading text instead of byediting. **This comment is the authoritative one**; the one above is byte-identicalin its JSON.)```json
{
"issue": 12342,
"status": "done",
"branch": "claude/issue-12342-client-sdk-catch-binding",
"pr": "https://github.com/objectstack-ai/objectstack/pull/12387",
"premise_still_valid": true,
"summary": "The card's defect is real and reproduced exactly: the two Error Handling examples on content/docs/api/client-sdk.mdx produce 5 and 2 TS18046 respectively under a strict profile copied from check:skill-examples' own tsconfig. Both blocks now narrow through an `isApiError` guard the first block declares, in the same continuation style the page already uses for `client`; block 1 is otherwise self-contained. No os:check markers were added (Zone 1.2), so the corrected examples remain unverified by CI by construction — the PR body says this in its own section rather than letting the green checks imply otherwise. ZONE 2 RESULTS: (a) CONFIRMED by measurement, not by this repo's tsconfig — a standalone harness with strict:true, target ES2020, types:[] reproduces the card's per-block counts exactly. (b) PARTLY FALSIFIED, and this changed the fix: `error.code` IS a property the SDK sets, but the triage's suggested narrowing target `StandardError` is the WRONG shape on three measured counts — its `code` is the closed StandardErrorCode enum which does NOT contain `VALIDATION_FAILED` (that code is registered in ERROR_CODE_LEDGER, and it is the exact value both examples branch on, so typing code as StandardErrorCode would turn the page's own comparison into a compile error); it declares no `fields` member, which is the entire subject of block 2; and it requires category/retryable which the flat REST envelope never carries. StandardError describes the server's error ENVELOPE, not the thrown object. Your charter boundary equated 'not reliably StandardError-shaped' with 'no supported way to narrow' — those measured DIFFERENT: the thrown value is always a real Error and always carries httpStatus (error.httpStatus = res.status, unconditional at the !res.ok seam), and carries it on none of the pre-response throws, so httpStatus is an honest, teachable discriminator. So the operative stop condition was false and I proceeded; no new export was added. (c) COUNTED: THREE blocks on the page have the shape, not two. Fixed the two the card names; left the automation.execute block inside the API-surface tour fence and filed it. ALSO TOUCHED, both declared in the PR body: the page's MDX header comment, which carried a now-false sentence claiming these blocks could only be made to compile by 'teaching worse code than the page teaches now'; and one prose sentence claiming error.code 'is always' a string, which is measurably not always (it is undefined when the server sent neither spelling). DISCLOSURES: (1) I pushed the empty probe branch first as required, then rebased onto a fresher origin/main after dispatch-gates reported STALE TREE, which required one `git push --force-with-lease` on my own branch before any PR existed. No other work could have been on that ref. Next time the probe should be followed by a merge rather than a rebase. (2) The REST channel is unreachable from this seat — every api.github.com call returns 403 'GitHub access is not enabled for this session', which is the condition already filed as #12293. The skip-changeset label therefore went through the declared read-union-write fallback (read: ['size/s'] with the size labeler already settled, wrote ['size/s','skip-changeset']), and read-back confirms both are present. Dedup for the two new findings used MCP list_issues over 60 recent open issues plus local grep, because MCP search_issues returned total_count 0 even for the known-open #12342 — its index is not answering, so a 0 from it was not treated as evidence. (3) The report comment's HTML-comment marker did not survive the body sanitizer; restored as literal text in this re-post because no edit route exists from this seat.",
"tests": "All 23 gate families derived by `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (clean derivation, no STALE TREE) run at final commit d87b4d5ecf — every exit code captured into a variable before any pipe, never read off a tail. All 23 EXIT=0: check:cross-package-test-inputs, check:doc-anchors, check:doc-authoring, lint/check:doc-formula-expressions, lint/check:doc-security-posture, spec/check:docs, check:docs-audit-scope, check:docs-redirects, check:docs-single-h1, spec/check:empty-state, spec/check:liveness, check:published-readme-links, check:react-page-adapter-contract, check:role-word, spec/check:skill-examples, spec/check:strictness-ledger, spec/check:variant-docs, check-ci-filter-parity.mjs, check-cross-package-test-inputs.mjs, check-doc-frontmatter.mjs, check-doc-route-spelling.mjs, check-docs-section-name.mjs, check-section-landing-index.mjs. check:skill-examples prints its own verdict line: '260 prose examples type-check across 3 surface(s) — every marked block parsed, so tsc ran the SEMANTIC pass on all of them'. Two of these were red on first run with PREREQUISITE NOT MET (unbuilt @objectstack/formula and @objectstack/lint) — nothing measured, not findings; a build of formula/lint/client/client-react/spec through `bash scripts/pm/os-verify-lock.sh -c ...` (VERDICT command-exit 0, held 178s, waited 2s, 33/33 tasks) cleared them and both then passed. THE CARD'S OWN MEASUREMENT, reproduced in a standalone tsc program using the compilerOptions check-skill-examples writes for its build dir (strict:true, target ES2020, module ESNext, moduleResolution bundler, lib ES2020+DOM+DOM.Iterable, types:[]), tsc 6.0.3, with `client` and `showFieldError` declared so the page's separate TS2304 continuation convention is excluded: BEFORE, exit 2 with exactly 7 diagnostics — before_block1.ts lines 4-8 all 'error TS18046: error is of type unknown' (5), before_block2.ts(4,7) and (5,21) the same (2). That is the card's 5+2 to the line. AFTER, the two blocks EXTRACTED PROGRAMMATICALLY FROM THE COMMITTED FILE (not from a draft) and compiled in the same program: exit 0, zero diagnostics. Block 1 also compiles alone against nothing but a declared `client`, confirming its guard is self-contained. NO ABLATION was run — there is no gate to prove failable here, which is precisely the Zone 1.2 tension: no marker means no CI program compiles these blocks at all. REPO-WIDE `pnpm lint` NOT RUN, and the narrowing is proven rather than asserted: asked through eslint's own config resolution (new ESLint({allowInlineConfig:false}) in this tree), `isPathIgnored('content/docs/api/client-sdk.mdx')` returns true and lintFiles reports 'File ignored because no matching configuration was supplied.' So (1) the population comes from eslint's config, not my guess; (2) the lintable-file count in this diff is 0, read from the lintFiles result; (3) no config applies to the path at all, so no type-aware linting exists through which this diff could move an untouched file's verdict. No changeset: `content/` is on changeset-check's own 'releases nothing' list in .github/workflows/pr-automation.yml, and two recent docs-only merges (e3430a2cb5, b1581b044d) carry none. Control-byte scan on the edited file clean (grep -naP over the C0 set plus DEL: no match).",
"open_questions": [
{
"question": "`@objectstack/client` exports a `StandardError` interface that does not describe the object the client throws — its `code` is the closed StandardErrorCode enum (missing the ledger-registered VALIDATION_FAILED the SDK itself normalizes to), it declares no `fields`, and it requires category/retryable which the flat REST envelope never sets. Every consumer narrowing a caught SDK error must therefore hand-write the shape, as the docs example now does. Should the SDK own this?",
"options": [
"A — leave it: the docs now teach the guard explicitly, no public surface grows, and consumers keep full freedom over how much of the shape they depend on.",
"B — export a `ThrownApiError` type plus an `isApiError` guard from @objectstack/client, and have the docs import them instead of declaring them inline.",
"C — fix `StandardError` itself so it describes the thrown object (add fields, widen code, make category/retryable optional).",
"D — retire or rename `StandardError` to say what it is (the server envelope), independent of whether a thrown-error type is added."
],
"recommendation": "A for now, with D worth its own card. Real business need: nothing measured pulls for B — no in-repo consumer imports StandardError to narrow a catch, so B would be a speculative public-surface expansion, and the dispatch charter explicitly declined it as a rider. Long-term soundness: the guard belongs at the producer eventually, but adding a public export whose only consumer today is a docs snippet is the workaround shape, not the contract-first one; D is the contract-first move, because a type whose NAME says 'error' and whose CONTENT is 'wire envelope' is exactly what made this card cost a measurement. Hard-to-get-wrong for AI-written code: A is neutral and D is a strict improvement — the current name invites precisely the wrong narrowing, and I nearly wrote it before checking the enum. Startup scope discipline: B and C both widen or reshape a published surface for zero measured pull; D is a naming/precision fix with no new capability. So: ship A here, file D for triage if the maintainer agrees the name is the defect."
}
],
"out_of_scope_findings": [
"filed as #12388: a THIRD example on the same page (the automation.execute block inside the API-surface tour fence) reads err.httpStatus/err.code/err.details off an untyped catch binding — 4 more would-be TS18046, left alone because it is a fragment inside a ~150-line multi-section fence and narrowing it is a shaping decision, not a mechanical edit",
"filed as #12389: the page's Error Codes table lists nine StandardErrorCode members and omits VALIDATION_FAILED, the ledger-registered code its own two examples branch on — a reader working from the table writes VALIDATION_ERROR and never matches; the table also nowhere says the vocabulary is StandardErrorCode union the serving side's ledger"
]
}
Activity
content/docs/api/client-sdk.mdx— the most SDK-dense page in the docs — has 13 TypeScript fences and 0os:checkmarkers, so none of them is ever compiled #11942yinlianghui commentedon Aug 25, 2026
Claim:devx@objectstack seat (#6023) · sessionsession_01UjM2ia8Av1v5NqfqQEQmC6· R9Branch
claude/issue-12342-client-sdk-catch-binding· dedicated worktree · model tier: standard.Declared file surface: the two error-handling examples on
content/docs/api/client-sdk.mdx. ⛔ Not the whole page.Serial:⚠️ #11942 is
pm:blockedon this same file — it is about that page's 13 TypeScript fences carrying 0os:checkmarkers, so none of them is ever compiled. No open PR claims the file, so it is free, but see Zone 1.2.Zone 1 — rulings (scope, not facts; not renegotiable)
os:checkmarkers. That is [finding]content/docs/api/client-sdk.mdx— the most SDK-dense page in the docs — has 13 TypeScript fences and 0os:checkmarkers, so none of them is ever compiled #11942's whole subject and it is blocked.content/docs/releases/**.premise_still_valid: false.Zone 2 — my assumptions; falsify them before building on them
a. I take the card's reading that both examples read⚠️ I have not checked the repo's own ⚠️ I assume
error.codeoff an untypedcatchbinding and so do not compile under a reader'suseUnknownInCatchVariables.tsconfigsetting for it — the claim is about a reader's strict project, not this repo's, so do not "verify" it against local settings and call it settled.b.
error.codeis the right shape to narrow to. Read what the client SDK actually throws before you write the narrowing — a corrected example that compiles but names a property the SDK never sets is worse than the current one, which at least fails loudly.c. I have not counted whether only 2 examples on the page have this shape. If there are more, report the count; fix the 2 the card names and say what you left.
Zone 3 — suggested route (not binding)
Read what the SDK throws → write the narrowing that is both compilable and true → keep the examples short enough to stay readable. If a shared error type is exported for exactly this, prefer it over a hand-written interface.
Gate union at the final commit; re-derive on
STALE TREE. Run the docs link checker if it is in the union. Exit codes captured before any pipe.Generated by Claude Code
yinlianghui commentedon Aug 26, 2026
{ "issue": 12342, "status": "done", "branch": "claude/issue-12342-client-sdk-catch-binding", "pr": "https://github.com/objectstack-ai/objectstack/pull/12387", "premise_still_valid": true, "summary": "The card's defect is real and reproduced exactly: the two Error Handling examples on content/docs/api/client-sdk.mdx produce 5 and 2 TS18046 respectively under a strict profile copied from check:skill-examples' own tsconfig. Both blocks now narrow through an `isApiError` guard the first block declares, in the same continuation style the page already uses for `client`; block 1 is otherwise self-contained. No os:check markers were added (Zone 1.2), so the corrected examples remain unverified by CI by construction — the PR body says this in its own section rather than letting the green checks imply otherwise. ZONE 2 RESULTS: (a) CONFIRMED by measurement, not by this repo's tsconfig — a standalone harness with strict:true, target ES2020, types:[] reproduces the card's per-block counts exactly. (b) PARTLY FALSIFIED, and this changed the fix: `error.code` IS a property the SDK sets, but the triage's suggested narrowing target `StandardError` is the WRONG shape on three measured counts — its `code` is the closed StandardErrorCode enum which does NOT contain `VALIDATION_FAILED` (that code is registered in ERROR_CODE_LEDGER, and it is the exact value both examples branch on, so typing code as StandardErrorCode would turn the page's own comparison into a compile error); it declares no `fields` member, which is the entire subject of block 2; and it requires category/retryable which the flat REST envelope never carries. StandardError describes the server's error ENVELOPE, not the thrown object. Your charter boundary equated 'not reliably StandardError-shaped' with 'no supported way to narrow' — those measured DIFFERENT: the thrown value is always a real Error and always carries httpStatus (error.httpStatus = res.status, unconditional at the !res.ok seam), and carries it on none of the pre-response throws, so httpStatus is an honest, teachable discriminator. So the operative stop condition was false and I proceeded; no new export was added. (c) COUNTED: THREE blocks on the page have the shape, not two. Fixed the two the card names; left the automation.execute block inside the API-surface tour fence and filed it. ALSO TOUCHED, both declared in the PR body: the page's MDX header comment, which carried a now-false sentence claiming these blocks could only be made to compile by 'teaching worse code than the page teaches now'; and one prose sentence claiming error.code 'is always' a string, which is measurably not always (it is undefined when the server sent neither spelling). DISCLOSURES: (1) I pushed the empty probe branch first as required, then rebased onto a fresher origin/main after dispatch-gates reported STALE TREE, which required one `git push --force-with-lease` on my own branch before any PR existed. No other work could have been on that ref. Next time the probe should be followed by a merge rather than a rebase. (2) The REST channel is unreachable from this seat — every api.github.com call returns 403 'GitHub access is not enabled for this session', which is the condition already filed as #12293. The skip-changeset label therefore went through the declared read-union-write fallback (read: ['size/s'] with the size labeler already settled, wrote ['size/s','skip-changeset']), and read-back confirms both are present. Dedup for the two new findings used MCP list_issues over 60 recent open issues plus local grep, because MCP search_issues returned total_count 0 even for the known-open #12342 — its index is not answering, so a 0 from it was not treated as evidence.", "tests": "All 23 gate families derived by `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (clean derivation, no STALE TREE) run at final commit d87b4d5ecf — every exit code captured into a variable before any pipe, never read off a tail. All 23 EXIT=0: check:cross-package-test-inputs, check:doc-anchors, check:doc-authoring, lint/check:doc-formula-expressions, lint/check:doc-security-posture, spec/check:docs, check:docs-audit-scope, check:docs-redirects, check:docs-single-h1, spec/check:empty-state, spec/check:liveness, check:published-readme-links, check:react-page-adapter-contract, check:role-word, spec/check:skill-examples, spec/check:strictness-ledger, spec/check:variant-docs, check-ci-filter-parity.mjs, check-cross-package-test-inputs.mjs, check-doc-frontmatter.mjs, check-doc-route-spelling.mjs, check-docs-section-name.mjs, check-section-landing-index.mjs. check:skill-examples prints its own verdict line: '260 prose examples type-check across 3 surface(s) — every marked block parsed, so tsc ran the SEMANTIC pass on all of them'. Two of these were red on first run with PREREQUISITE NOT MET (unbuilt @objectstack/formula and @objectstack/lint) — nothing measured, not findings; a build of formula/lint/client/client-react/spec through `bash scripts/pm/os-verify-lock.sh -c ...` (VERDICT command-exit 0, held 178s, waited 2s, 33/33 tasks) cleared them and both then passed. THE CARD'S OWN MEASUREMENT, reproduced in a standalone tsc program using the compilerOptions check-skill-examples writes for its build dir (strict:true, target ES2020, module ESNext, moduleResolution bundler, lib ES2020+DOM+DOM.Iterable, types:[]), tsc 6.0.3, with `client` and `showFieldError` declared so the page's separate TS2304 continuation convention is excluded: BEFORE, exit 2 with exactly 7 diagnostics — before_block1.ts lines 4-8 all 'error TS18046: error is of type unknown' (5), before_block2.ts(4,7) and (5,21) the same (2). That is the card's 5+2 to the line. AFTER, the two blocks EXTRACTED PROGRAMMATICALLY FROM THE COMMITTED FILE (not from a draft) and compiled in the same program: exit 0, zero diagnostics. Block 1 also compiles alone against nothing but a declared `client`, confirming its guard is self-contained. NO ABLATION was run — there is no gate to prove failable here, which is precisely the Zone 1.2 tension: no marker means no CI program compiles these blocks at all. REPO-WIDE `pnpm lint` NOT RUN, and the narrowing is proven rather than asserted: asked through eslint's own config resolution (new ESLint({allowInlineConfig:false}) in this tree), `isPathIgnored('content/docs/api/client-sdk.mdx')` returns true and lintFiles reports 'File ignored because no matching configuration was supplied.' So (1) the population comes from eslint's config, not my guess; (2) the lintable-file count in this diff is 0, read from the lintFiles result; (3) no config applies to the path at all, so no type-aware linting exists through which this diff could move an untouched file's verdict. No changeset: `content/` is on changeset-check's own 'releases nothing' list in .github/workflows/pr-automation.yml, and two recent docs-only merges (e3430a2cb5, b1581b044d) carry none. Control-byte scan on the edited file clean (grep -naP over the C0 set plus DEL: no match).", "open_questions": [ { "question": "`@objectstack/client` exports a `StandardError` interface that does not describe the object the client throws — its `code` is the closed StandardErrorCode enum (missing the ledger-registered VALIDATION_FAILED the SDK itself normalizes to), it declares no `fields`, and it requires category/retryable which the flat REST envelope never sets. Every consumer narrowing a caught SDK error must therefore hand-write the shape, as the docs example now does. Should the SDK own this?", "options": [ "A — leave it: the docs now teach the guard explicitly, no public surface grows, and consumers keep full freedom over how much of the shape they depend on.", "B — export a `ThrownApiError` type plus an `isApiError` guard from @objectstack/client, and have the docs import them instead of declaring them inline.", "C — fix `StandardError` itself so it describes the thrown object (add fields, widen code, make category/retryable optional).", "D — retire or rename `StandardError` to say what it is (the server envelope), independent of whether a thrown-error type is added." ], "recommendation": "A for now, with D worth its own card. Real business need: nothing measured pulls for B — no in-repo consumer imports StandardError to narrow a catch, so B would be a speculative public-surface expansion, and the dispatch charter explicitly declined it as a rider. Long-term soundness: the guard belongs at the producer eventually, but adding a public export whose only consumer today is a docs snippet is the workaround shape, not the contract-first one; D is the contract-first move, because a type whose NAME says 'error' and whose CONTENT is 'wire envelope' is exactly what made this card cost a measurement. Hard-to-get-wrong for AI-written code: A is neutral and D is a strict improvement — the current name invites precisely the wrong narrowing, and I nearly wrote it before checking the enum. Startup scope discipline: B and C both widen or reshape a published surface for zero measured pull; D is a naming/precision fix with no new capability. So: ship A here, file D for triage if the maintainer agrees the name is the defect." } ], "out_of_scope_findings": [ "filed as #12388: a THIRD example on the same page (the automation.execute block inside the API-surface tour fence) reads err.httpStatus/err.code/err.details off an untyped catch binding — 4 more would-be TS18046, left alone because it is a fragment inside a ~150-line multi-section fence and narrowing it is a shaping decision, not a mechanical edit", "filed as #12389: the page's Error Codes table lists nine StandardErrorCode members and omits VALIDATION_FAILED, the ledger-registered code its own two examples branch on — a reader working from the table writes VALIDATION_ERROR and never matches; the table also nowhere says the vocabulary is StandardErrorCode union the serving side's ledger" ] }Generated by Claude Code
yinlianghui commentedon Aug 26, 2026
os-dev-report
(Re-post of the report in the comment directly above. That one led with the
<!-- os-dev-report -->HTML-comment marker the contract asks for, and GitHub'sbody sanitizer ate it — read-back shows the stored body starting at the
Generated by Claude Code