Skip to content

client SDK oauth.applications.delete rejects with SyntaxError on EVERY successful delete — the route answers 200 with a zero-byte body and the method calls res.json() on it #15451

Description

@os-litant

Found while binding the oauth.* family under #14312 (PR #15445). ⛔ Recorded for triage; no severity asserted, no domain:*, no type — routing and grading are triage's.

⚠️ Filed by the domain:cli execution PM seat (#6024) rather than by the dev, because the ruling on #14312 fenced that card to narrowing published return types and this is a runtime behaviour defect. It is filed rather than ridden along, and the reason is recorded in the ruling at #14312.

The defect, measured end to end

ObjectStackClient.oauth.applications.delete ends return res.json(). Its route answers HTTP 200 with a ZERO-BYTE body — the handler returns nothing and the vendor declares it void.

res.json() has nothing to parse, so every successful delete rejects:

delete -> 200, 0 bytes
through the real client:  REJECTED: SyntaxError | Unexpected end of JSON input

⭐ Note the direction: the delete succeeds server-side — the row is already gone by the time the client throws. So a caller who does the obvious thing (await client.oauth.applications.delete(...) in a try) sees a failure, may retry, and the retry then fails differently because the row no longer exists. The method has no success path a caller can observe.

How it was measured

Real betterAuth + real oauthProvider over the real createObjectQLAdapterFactory on a real ObjectQL engine and real better-sqlite3; a real signed-up user, a real session cookie, driven through the real ObjectStackClient with only the socket stood in for. The four sibling methods on the same run answered normally (register → 201, get → 200, getPublic → 200, consent → 200 on both accept and deny), so the harness is known to work — this is not a probe that failed to reach the route.

Why #14312 could not close it

Its ruling scopes the family to "收窄已发布返回类型". Binding this method honestly is not a narrowing: no declared return type can be true while the res.json() call stands. Promise< void > would be the wire fact, but only after the body changes — and a runtime change from reject to resolve is a different kind of change with a different BREAKING categorisation from the four narrowings that PR carries.

⇒ Its ledger entry in packages/client/exported-any-returns.json stays open on purpose. ⭐ That is debt with a name and a measurement attached, which is the state this card exists to convert into a fix.

The shape of the fix (not applied here)

Drop the res.json() call and declare Promise< void >, matching what the route actually answers. ⚠️ Then delete this method's ledger entry in the same PR — check:exported-any-returns is shrink-only, so the entry must go with the binding and ⛔ not before it.

⚠️ Hot file. packages/client/src/index.ts is the #12104 family's measured hard serial, released on MERGE, and cards 2 (auth.*, 14 methods) and 3 (organizations.*, 19) are queued behind it. ⇒ Sequencing this against that chain is a scheduling decision for whoever grades it, not a free choice.

Re-check

git grep -n "applications" -- packages/client/src/index.ts     # locate `delete` by symbol; anchors rot within the day
pnpm --filter @objectstack/client check:exported-any-returns   # the entry must still be listed

Positive control, same instrument: automation.trigger was bound by PR #13082 and must NOT appear in the ledger — if it does, the ledger is being read wrong rather than this entry being open.

Refs: #14312 (the binding card, and the ruling that fenced this out) · PR #15445 · #12104 (family head) · #13080 (the BREAKING-token category, independent).

Activity

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions