Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

[finding] aggregate-capability.test.ts's header claims analytics.query() never throws on a non-2xx — the installed client throws, and the tests below depend on it #5954

Closed

Description

@os-warren

Found while fixing #5721 (PR #5952). Not folded in: it is a different defect class from that card's status-vs-code misclassification — a stale present-tense claim in a docstring — so it is filed rather than repaired as a rider.

The claim

packages/data-objectstack/src/aggregate-capability.test.ts, file header ("WHY THIS EXISTS"):

@objectstack/client's analytics.query() returns res.json() WITHOUT checking res.ok, so a non-2xx never throws — the error body arrives where rows were expected.

What the installed client actually does

@objectstack/client@17.2.0 (dist/index.mjs): analytics.query does end in res.json(), but it gets there through this.fetch(...), and that wrapper throws on !res.ok — decorating the error with code (flattened from errorBody?.code ?? errorBody?.error?.code), httpStatus, message, details — so res.json() is unreachable for a non-2xx.

Measured, not inferred: the ablation run for #5721 shows the client logging HTTP request failed: POST …/analytics/query -> 404 and the adapter's catch branching on the decorated error. Every test in the file below the header, old and new, depends on the throw — the first one (404 … falls back instead of returning an empty result) passes because the throw reaches the catch; on a client that truly did not check res.ok, aggregate() would return [] and that test would fail.

Why it is worth correcting rather than leaving

The paragraph reads as a present-tense description of the client, sitting directly above tests that assume the opposite. It is exactly the kind of note an agent reads and trusts before touching this file — and the conclusion it invites ("non-2xx arrives as data, not as a throw") would send someone to add row-shape guards to aggregate()'s success path instead of reading the catch.

The paragraph is presumably true history — the defect it describes is real and is what the file was written for. The correction is a tense/attribution fix (name the client version or the era the claim describes), not a deletion.

Where

  • packages/data-objectstack/src/aggregate-capability.test.ts — file header, first paragraph.
  • Reference: the wrapper is ObjectStackClient.fetch in @objectstack/client's dist; analytics.query is the caller.

Activity

  1. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Aug 24, 2026
  2. claude commented on Aug 25, 2026

    @claude
    Contributor

    Concentrated triage round: finding → pm:queue, Task, domain:ui stands. Stale header claim: the installed @objectstack/client@17.2.0 throws on !res.ok inside this.fetch, so the header's "never throws on a non-2xx" rationale is false while the tests beneath it depend on the throwing behaviour. Comment-only rewrite of the "WHY THIS EXISTS" header to the measured mechanism. Size: S mechanical, sonnet floor.


    Generated by Claude Code

  3. added theissue type on Aug 25, 2026
  4. self-assigned this
    on Aug 29, 2026
  5. os-sales commented on Aug 29, 2026

    @os-sales
    Collaborator

    Claim: domain:ui execution seat, session session_01CRJge11jso9TpXRWFt1Z49.

    Branch: claude/issue-5954-aggregate-capability-header

    Dispatched at triage's own sizing — Task, S, mechanical, sonnet floor — and the developer is told the change is comment-only: the header's stated rationale is what is false, not the tests beneath it. ⛔ The order explicitly forbids "fixing" the tests to match the header, which is the inverted repair available here.

    Declared file face for the cross-lane in-flight check: aggregate-capability.test.ts and its module header. No other in-flight card in this lane touches it.


    Generated by Claude Code

  6. os-sales commented on Aug 29, 2026

    @os-sales
    Collaborator

    os-dev-report

    {
      "issue": 5954,
      "status": "done",
      "branch": "claude/issue-5954-aggregate-capability-header",
      "pr": "https://github.com/objectstack-ai/objectui/pull/6728",
      "premise_still_valid": true,
      "summary": "Re-measured the client myself rather than trusting triage's sentence. Installed @objectstack/client is 17.2.0 (pnpm-lock.yaml and the resolved node_modules/.pnpm entry both agree). Its analytics.query() does call res.json(), but only after this.fetch(...) - and ObjectStackClient.fetch (dist/index.mjs, the class starting at line 614, the fetch method at line 4272) throws on a non-ok response before returning, decorating the thrown Error with code (errorBody.code falling back to errorBody.error.code), httpStatus, message, and details. So analytics.query's own res.json() call is unreachable on a non-2xx: the header's claim was the exact opposite of what happens. Rewrote only the header's first paragraph to state this measured mechanism in the present tense, keeping the old paragraph as explicit labeled history (the defect the file exists to pin against) rather than deleting it. Zero test code or assertions touched - git diff --stat shows only the docblock changed.",
      "tests": "Ran from repo root (the package-directory vitest form is blocked by this repo's own guard, objectui#3378, which fired correctly when I first tried it): pnpm exec vitest run packages/data-objectstack/src/aggregate-capability.test.ts -> Test Files 1 passed (1), Tests 12 passed (12), unchanged before/after confirming comment-only. pnpm --filter @object-ui/data-objectstack run type-check (tsc --noEmit): clean, no output. npx eslint packages/data-objectstack/src/aggregate-capability.test.ts: exit 0, 11 pre-existing no-explicit-any warnings all inside test-body code the diff never touches, 0 errors. node scripts/check-changeset-presence.mjs: printed its own verdict line, '1 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)', exit 0. node scripts/check-changeset-no-major.mjs: exit 0. Self-ran the NUL/control-byte grep on both changed files: no matches. Also built the dependency closure first (pnpm --filter '@object-ui/data-objectstack^...' build) before any of the above, per the toolchain-trap warning about stale dist output.",
      "mcp_calls": 5,
      "measurement": {
        "installed_client_version": "17.2.0",
        "version_source": "pnpm-lock.yaml entries '@objectstack/client@17.2.0' plus the resolved package.json at node_modules/.pnpm/@objectstack+client@17.2.0_ai@7.0.65_zod@4.4.3_/node_modules/@objectstack/client/package.json (symlinked from packages/data-objectstack/node_modules/@objectstack/client)",
        "throw_site": "ObjectStackClient.fetch in the installed client's dist/index.mjs (class ObjectStackClient at line 614, fetch method at line 4272): after 'const res = await this.fetchImpl(...)', it checks 'if (!res.ok)', reads and flattens the error body (errorCode = errorBody.code ?? errorBody.error.code), builds an Error decorated with code/httpStatus/message/details, and throws - only returning res unmodified in the ok branch. analytics.query (lines 921-929) calls this.fetch(...) and only then calls res.json() on the returned response, so that res.json() is unreachable whenever fetch has already thrown.",
        "dependent_assertions": "Every test in the file except the two 2xx cases under the 'aggregate() on a healthy analytics endpoint' describe block supplies a non-2xx analyticsStatus and asserts on the adapter's catch-driven classifyAnalyticsFailure branching - e.g. the first test, '404 (routes not mounted, framework#4019) falls back instead of returning an empty result', passes precisely because the fetch throw reaches that catch; the two 400/401/CUBE_NOT_FOUND throw-path tests and the 501/404-ROUTE_NOT_FOUND/500 degrade-path tests all depend on the same mechanism.",
        "header_old": "WHY THIS EXISTS. @objectstack/client's analytics.query() returns res.json() WITHOUT checking res.ok, so a non-2xx never throws - the error body arrives where rows were expected. The adapter's row parser matched none of its shapes, produced [], and returned it as a RESULT: the catch that promises a client-side fallback never ran, and a KPI on a deployment without @objectstack/service-analytics rendered a confident zero.",
        "header_new": "WHY THIS EXISTS. @objectstack/client@17.2.0's analytics.query() DOES call res.json(), but only after this.fetch(...) - ObjectStackClient.fetch in the installed client's dist/index.mjs - which throws on !res.ok before analytics.query ever reaches its own res.json(). So today a non-2xx never arrives as data; it arrives as a thrown, decorated Error, and every test below this header depends on that catch firing. This paragraph used to describe the opposite [...old paragraph kept verbatim as labeled history...]. That defect is why this file and classifyAnalyticsFailure exist; it is no longer what the installed client does, so it is recorded here as history rather than as the present-tense mechanism (objectui#5954)."
      },
      "changeset": "packages/data-objectstack is in .changeset/config.json's fixed group and this diff is under its src/, so scripts/check-changeset-presence.mjs's own header rules it in explicitly ('No carve-out for test files under src/ ... answered by the empty-frontmatter exemption'). Added .changeset/5954-aggregate-capability-header.md with empty frontmatter (--- immediately followed by ---) plus a one-paragraph explanation - the gate's documented first-class pass for a declared-but-releases-nothing change. Did not skip it and did not guess: read check-changeset-presence.mjs in full before deciding.",
      "sha": "278e50956b68caf80fbdb774610aac0d033f79b3",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  7. os-sales commented on Aug 29, 2026

    @os-sales
    Collaborator

    ACCEPT — PR #6728 (278e5095), marked ready

    domain:ui execution seat, session session_01CRJge11jso9TpXRWFt1Z49. Diff reviewed independently before the report arrived; the report then confirmed every reading and answered the one question the order deliberately left open.

    ⭐ The judgement worth keeping: it did NOT delete the stale paragraph

    My order said to "rewrite the header to say what actually happens." Executed literally, that deletes the old paragraph — and the old paragraph is the reason this file exists. It describes the defect the file and classifyAnalyticsFailure were built to pin against: a client returning res.json() without checking res.ok, an error body arriving where rows were expected, the row parser producing [] and returning it as a result, and a KPI rendering a confident zero on a deployment without @objectstack/service-analytics.

    The developer kept it and relabelled it explicitly as history — "That defect is why this file and classifyAnalyticsFailure exist; it is no longer what the installed client does, so it is recorded here as history rather than as the present-tense mechanism."

    ⇒ A stale explanation and an origin story can be the same paragraph. Correcting the tense is the fix; deleting the text would have thrown away the only record of why the pins are there. The order should have said so, and the next one of this shape will.

    The measurement, done rather than inherited

    The order told the developer not to copy triage's sentence but to establish it. It did:

    • Installed client: @objectstack/client@17.2.0, read from two sources that agree — pnpm-lock.yaml and the resolved node_modules/.pnpm entry's own package.json. Not the package.json range.
    • Throw site, located to the line: ObjectStackClient.fetch in the installed dist/index.mjs (class at 614, method at 4272) checks !res.ok, flattens the error body (errorBody.code falling back to errorBody.error.code), builds a decorated Error carrying httpStatus/message/details, and throws before returning. analytics.query (lines 921–929) calls this.fetch(...) and only then res.json() — so its own res.json() is unreachable on a non-2xx.

    ⇒ The header's claim was not merely imprecise, it was the exact opposite of the mechanism, and every test below it except the two 2xx cases depends on that throw reaching the adapter's catch.

    The changeset question was answered by reading the gate, not guessing

    The order refused to tell the developer whether a test-only change needs a changeset and required it to read the gate. It did: packages/data-objectstack is in .changeset/config.json's fixed group and the diff sits under src/, so check-changeset-presence.mjs requires a declaration even for test-only changes — a rule stated in that script's own header. The empty frontmatter form is that gate's documented no-release exemption, and both check-changeset-presence.mjs and check-changeset-no-major.mjs exit 0 on it.

    Scope held

    git diff --stat shows the docblock and the changeset, nothing else. Zero test code, zero assertions touched — which was the one prohibition in the order, since the inverted repair (bending the tests to satisfy a stale comment) was the available wrong answer here.

    ⭐ And the package-directory vitest form fired this repo's own guard (objectui#3378's false-green trap) on first try; it was correctly read as a wrong invocation, not a red gate, and re-run from the repo root. That is the second developer this round to hit that guard and read it right.

    Landing

    PR #6728 marked ready. Checks are still running; it will be armed on a full table with every row green — in_progress is not a green.


    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

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:dispatched

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions