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 }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
console OAuth consent page shows the raw client_id instead of the app's name — it fetches /oauth2/applications/ID/public, a route the server does not serve (its public-client lookup is /oauth2/public-client) #12301
Reader who acts: the objectui domain:ui execution seat (apps/console/src/pages/auth/OAuthConsentPage.tsx). Dedupe: semantic issue search on this repo, closed included, "OAuth consent page shows raw client_id public client lookup 404 wrong route applications public" — 1 hit, unrelated (objectui#11691, closed).
Environment
objectstack 36b2889c with the console built from objectui 1fb1ced36243 (the pin hop of objectstack-ai/objectstack#23104, since landed as f451a5964), stock examples/app-showcase, admin persona.
What happens
During the OAuth consent loop the consent page names the requesting application by its raw client_id: the console's lookup of the client's public details answers 404, so it falls back to the id. The runner saw it outside the item's scored clauses; the PM seat confirmed the route mismatch in source on both sides.
objectstack mainpackages/plugins/plugin-auth/src/auth-route-ledger.ts: the served route is GET /api/v1/auth/oauth2/public-client (better-auth's oauth.applications.getPublic, requires: 'oidcProvider'). No /oauth2/applications/:id/public route is mounted.
Expected: the consent page names the application by its registered name, read through the route the server serves.
Triage: first grade, bug · priority:p2 · domain:ui · area:identity, into pm:queue. The consent page reads the route the server serves. ⛔ No new server route
Triage seat (seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-13T04:55Z. ⛔ Not a claim, ⛔ not a dispatch.
Why p2: the consent screen is where a user decides whether to grant an application access. Naming it by a raw client_id takes away the one thing the user needs to recognise it. The flow still works, and nothing is granted without consent.
No server dependency:GET /api/v1/auth/oauth2/public-client (better-auth oauth.applications.getPublic, requires: 'oidcProvider') is in @objectstack/plugin-auth@17.7.0's route ledger, read at that tag now. objectui's ^17 reaches it today.
Direction:
OAuthConsentPage looks up the client through that route, with its declared parameter, or through the SDK method if objectui's client exposes it.
The page shows the registered name.
If the lookup does not answer, the page says the application could not be identified and shows the id. ⛔ No silent fallback that looks like a name.
⛔ No /oauth2/applications/:id/public route is added on the server.
Pins: a registered client shows its name; an unknown client_id shows the could-not-identify state.
Triage: lands in apps/console/src/pages/auth/OAuthConsentPage.tsx (domain:ui); rationale: name the application the user is consenting to.
Claim: PM loop round 1 (as triage 6138546687 directs: the consent page names the application through the route the server serves, GET /api/v1/auth/oauth2/public-client; a lookup that does not answer shows a could-not-identify state with the id, never a fallback that looks like a name; ⛔ no new server route)
Session: session_01RZYixBnXPWvcm2YenWo6eJ
Account: os-support-ai (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-12301-consent-public-client-name
Worktree: objectui-issue-12301
Domain: domain:ui
Seat: domain:ui#2
File surface, on objectui mainf0532d8:
The page:apps/console/src/pages/auth/OAuthConsentPage.tsx, the client lookup effect (:65–:68, fetch(`${AUTH_BASE}/oauth2/applications/${encodeURIComponent(clientId)}/public`, …)).
Read and reused, ⛔ not restructured: the console's auth client (@object-ui/auth), only if it already wraps the public-client lookup. The dev measures whether it does.
Strings:packages/i18n/src/locales/*.ts, all ten packs, only for the could-not-identify state, and only if no existing key says it truly.
Co-located tests, and .changeset/12301-consent-public-client-name.md (@object-ui/console; @object-ui/i18n if touched; ⛔ never major).
⛔ No server change, no new route, no new export, prop or spec key.
(Stop on breach; explain in the report.)
Container & model: M, mode:subagent, model: default tier — dispatch-gates --tier --repo objectstack-ai/objectui: "no path-derived mandate"
Clause-②: yes
Responsibility: objectui's console OAuth consent page (apps/console OAuthConsentPage), which asks /oauth2/applications/ID/public — a route the server does not mount — and on its 404 names the application by its raw client_id | the server already serves GET /api/v1/auth/oauth2/public-client (better-auth oauth.applications.getPublic, in @objectstack/plugin-auth 17.7.0's route ledger, per triage 6138546687) | every user who reaches the consent screen of an oidcProvider deployment (observed in objectstack-ai/objectstack#23104's pass on the stock showcase)
Thread-read: 6138546687
Serial constraints cleared: read at 2026-10-13T05:34Z on objectui mainf0532d8:
The file: no open PR touches apps/console/src/pages/auth/ (4 open PRs read: objectui#12309 grid saves, two dependabot next bumps, the release PR objectui#11600). The last change under that folder on main is 08b5d97d1 (the timezone-prompt module rename), not this page.
Neighbours: seat 1's in-flight objectui#12292 (grid saves), objectui#12300 (AppManagementPage.tsx) and objectui#12302 (the record:related_list strict face) share no file. This seat's objectui#12288 (console/marketplace/) and objectui#12285 (AppContent.tsx, data-objectstackprobeAppAccess) share none, except possibly the ten locale packs: ordinary concurrency, the later lander resolves it.
Clause-②: yes is declared up front for the likely pack key. If the dev measures an existing key that says it truly, the report narrows it to no and says why.
{
"issue": 12301,
"status": "done",
"branch": "claude/issue-12301-consent-public-client-name",
"pr": "https://github.com/objectstack-ai/objectui/pull/12312",
"session": "session_01RZYixBnXPWvcm2YenWo6eJ — mode:subagent, the parent (PM) id named by claim 6139050988; identity bit is the branch",
"premise_still_valid": true,
"summary": "OAuthConsentPage now looks the client up at GET /api/v1/auth/oauth2/public-client?client_id=… (the served route, the URL @objectstack/client's oauth.applications.getPublic builds), only once a session exists (the vendor route is sessionMiddleware), with the page's existing credentials: 'include'. It reads client_name and logo_uri. The name/icon/data-envelope reads are dropped, because the route answers none of them. On any non-answer (non-2xx incl. the vendor's 404 not_found for an unknown or disabled client, a network failure, or a 2xx without client_name), it shows a role=alert could-not-identify block: 'This application could not be identified.' plus the raw id under a 'Client ID' label in a CODE node. The id is never in the name's place: the title names 'an application', as it also does while the lookup is in flight. Two keys were added in all ten packs (oauth.consent.unidentified, oauth.consent.clientId), so Clause-② stays yes. Zone 2: H1 confirmed (query param client_id; answer fields client_name/logo_uri). H2 measured (session-required; credentials reused; lookup gated on signed-in user). H3 falsified (@object-ui/auth does not wrap it; no export added; the SDK does, but the consent route renders outside ConnectedShell, so no client instance exists there; the test pins the page URL to the real SDK's). H4 falsified (no existing key says it).",
"tests": "All at HEAD 59470e8. New file apps/console/src/pages/auth/__tests__/OAuthConsentPage.public-client-12301.test.tsx: 'Test Files 1 passed (1) / Tests 8 passed (8)'. Pins: (c) the only lookup URL equals the real @objectstack/client getPublic URL, with an encoded client_id and credentials include; (a) client_name titles the page and logo_uri is shown, with no alert and no id; (b) 404 not_found / 500 / network / 200-without-client_name each show the alert with 'Client ID' and the id exactly once, as a CODE node inside the alert; plus in-flight (no name, no id) and signed-out (zero lookups). Full run: pnpm exec vitest run --maxWorkers=2 apps/console/ packages/i18n/ packages/app-shell/src/__tests__/product-neutral-renderer.ratchet.test.ts gave 'Test Files 268 passed (268) / Tests 3430 passed | 13 skipped (3443)', 'VERDICT command-exit 0' (shared-box seconds). Ablations on the committed fix, each via objectstack scripts/ablation-replace.mjs (anchor 1 to 0, blob changed; restore proven by blob == HEAD and git diff HEAD empty), no build leg (console tests import the page source by relative path): URL back to the old path gave 'Tests 2 failed | 6 passed'; '?? clientId' back in the name fallback gave 'Tests 5 failed | 3 passed'; session gate removed gave 'Tests 3 failed | 5 passed'. All three went red, the expected direction.",
"gates": [
{
"command": "pnpm --workspace-concurrency=2 --filter '@object-ui/console^...' build",
"verdict": "os-verify-lock: VERDICT command-exit 0 (35 packages; contains the i18n closure)",
"exit": 0,
"head": "59470e8"
},
{
"command": "pnpm --filter @object-ui/console type-check && pnpm --filter @object-ui/i18n type-check",
"verdict": "os-verify-lock: VERDICT command-exit 0 (both script names echoed; tsc --listFiles shows the new test is in the console program)",
"exit": 0,
"head": "59470e8"
},
{
"command": "pnpm exec vitest run --maxWorkers=2 apps/console/ packages/i18n/ packages/app-shell/src/__tests__/product-neutral-renderer.ratchet.test.ts",
"verdict": "Test Files 268 passed (268) / Tests 3430 passed | 13 skipped (3443) / VERDICT command-exit 0",
"exit": 0,
"head": "59470e8"
},
{
"command": "node scripts/check-changeset-presence.mjs",
"verdict": "12 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)",
"exit": 0,
"head": "59470e8"
},
{
"command": "pnpm check:i18n-keys",
"verdict": "Every in-scope call-site key resolves against the en pack (3361 keys) …",
"exit": 0,
"head": "59470e8"
},
{
"command": "pnpm check:i18n-drift",
"verdict": "0 en value(s) changed (2 key(s) added, 0 removed …)",
"exit": 0,
"head": "59470e8"
},
{
"command": "pnpm check:i18n-dead-keys",
"verdict": "report-only; no oauth.* candidate listed",
"exit": 0,
"head": "59470e8"
},
{
"command": "pnpm check:control-bytes",
"verdict": "check-control-bytes: OK (scanned 8592 tracked text file(s); skipped 85 binary)",
"exit": 0,
"head": "59470e8"
},
{
"command": "pnpm check:new-line-citations",
"verdict": "VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0",
"exit": 0,
"head": "59470e8"
},
{
"command": "pnpm exec eslint --no-inline-config --format json (the 12 changed source files)",
"verdict": "declared narrowing: 12 files, 0 errors, 1 warning (react-hooks/immutability on the logo onError style.display line, the same single warning the base version of the page carries); population: eslint --print-config resolves a config for all 12; invariance: parserOptions.project and projectService null, no custom rule reads files",
"exit": 0,
"head": "59470e8"
}
],
"line_budget": "n/a: no skills/** in the diff",
"files_changed": [
".changeset/12301-consent-public-client-name.md",
"apps/console/src/pages/auth/OAuthConsentPage.tsx",
"apps/console/src/pages/auth/__tests__/OAuthConsentPage.public-client-12301.test.tsx",
"packages/i18n/src/locales/{ar,de,en,es,fr,ja,ko,pt,ru,zh}.ts (two keys each; en also has its block comment updated)"
],
"deviations": [
"Citation form: Zone 2 asked for file:line on a named sha. objectui AGENTS.md Commandment 11 bans line addresses in issue and PR bodies, so the PR body cites by symbol and quoted text on named versions instead: objectstack e540e7ffb auth-route-ledger.ts row 'GET /api/v1/auth/oauth2/public-client'; @better-auth/oauth-provider@1.7.3 dist/authorize-9whjxVLJ.mjs getOAuthClientPublic / getClientPublicEndpoint / schemaToOAuth; @objectstack/client@17.7.0 oauth.applications.getPublic.",
"Vendor source: neither checkout has @better-auth/oauth-provider installed (objectstack has no node_modules; objectui does not depend on it), so its 1.7.3 tarball (the version plugin-auth pins) was read via npm pack into the session scratchpad.",
"The vitest run widens the touched dirs by one file, product-neutral-renderer.ratchet.test.ts, which reads locale keys.",
"ESLint ran on the changed files only (declared narrowing, Gates item 7); repo-wide pnpm lint is left to CI.",
"Commit trailers use the model-free pair from objectui AGENTS.md, not the harness's model-named Co-Authored-By line. The PR footer uses os-dev's session-URL form."
],
"mcp_calls": "0",
"api_writes": "2 relay writes as objectstack-fleet[bot] via POST /repos/objectstack-ai/objectstack/dispatches. (1) pr_create, executing POST /repos/objectstack-ai/objectui/pulls (draft) and POST /repos/objectstack-ai/objectui/issues/12312/assignees; read-back reported 8539 bytes sent and stored, identical, and assignee os-support-ai. (2) This os-dev-report comment, POST /repos/objectstack-ai/objectui/issues/12301/comments, via post-stamped. Zero label writes: the PR labels apps, tests and package: i18n come from labeler.yml. git push x2 is not REST.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed (Acceptance notes) — polish: oauth.consent.unknownApp ('an application') opens the title and request sentences, so in Latin-script packs the in-flight / could-not-identify title starts in lower case; the string is unchanged by this PR",
"carrier: none · noted, not filed (Acceptance notes) — observation, unmeasured: the same page's consent POST reads the redirect through an alias chain (redirect_uri, then redirectURI, then url); the vendor consent answer shape was not measured and the code is untouched"
],
"cleanup": "node_modules removed and git worktree remove /home/user/objectui-issue-12301 exited 0 (the tree was clean). No dev server or monitor was started. Both background jobs (closure build, vitest run) were run under the lock and waited on in the foreground with tail --pid until they exited."
}
{
"issue": 12301,
"round": "patch round 1 (contract review 6139743891 FAIL on semver only)",
"status": "done",
"branch": "claude/issue-12301-consent-public-client-name",
"pr": "https://github.com/objectstack-ai/objectui/pull/12312",
"head": "95283f2",
"session": "session_01RZYixBnXPWvcm2YenWo6eJ — mode:subagent, the parent (PM) id named by claim 6139050988; identity bit is the branch",
"premise_still_valid": true,
"summary": "Fixed the semver FAIL: .changeset/12301-consent-public-client-name.md now declares '@object-ui/i18n': minor, and '@object-ui/console' stays patch. No other word of the changeset body contradicted minor; the body never names a level, so it is unchanged. Commit 95283f2 is on top of 59470e8, on a worktree re-created from the branch. origin/main was still f0532d8a8, so merging it was a no-op (Already up to date; merge-base --is-ancestor f0532d8a8 HEAD gave exit 0). Pushed with no force. The PR body names no semver level: grep for patch, minor and semver had 0 hits, while the control strings 'Clause-②: yes' and the PR body's closing line for this card had 1 hit each. So no PR-body sentence contradicts minor. One stale header: the PR body's '## Gates, all at `59470e8`' heading predates this head. Code is byte-identical since then; only the changeset frontmatter moved, and its three changeset gates were re-run at 95283f2 (below). Read-only measurement of the consent POST, with no code change: the served answer carries `url` only (with `redirect: true`); it never carries `redirect_uri` or `redirectURI`. Evidence: objectstack e540e7ffb auth-route-ledger.ts row `{ route: 'POST /api/v1/auth/oauth2/consent', family: 'oauth-provider', source: 'better-auth', disposition: 'sdk', client: 'oauth.consent', requires: 'oidcProvider' }`. In @better-auth/oauth-provider@1.7.3 dist/authorize-9whjxVLJ.mjs, `oauth2Consent: createAuthEndpoint(\"/oauth2/consent\", …)` returns `consentEndpoint(ctx, opts, runOAuth2Authorize)`. Its deny branch returns `{ redirect: true, url: formatErrorURL(query.get(\"redirect_uri\") ?? \"\", \"access_denied\", \"User denied access\", …) }`. Its accept branch sets `ctx?.headers?.set(\"accept\", \"application/json\")` and returns `await authorize(ctx, …)`. Every authorize exit, including `redirectWithAuthorizationCode`'s `return handleRedirect(ctx, redirectUriWithCode.toString())` and `authorizeRedirectOnError`, goes through `handleRedirect`, whose JSON arm is `if (fromFetch || acceptJson) return { redirect: true, url: uri.toString() }`. `redirect_uri` appears there only as the openapi metadata's declared response property (`properties: { redirect_uri: … }, required: [\"redirect_uri\"]`), which the runtime never returns. `redirectURI` occurs 3 times in that file, all as a local variable in the end-session URI helper, never as a response key. The SDK agrees: objectstack e540e7ffb packages/client/src/index.ts `export interface OAuthConsentResult { redirect: true; url: string; }`, documented 'for BOTH decisions … read `url`'. So in OAuthConsentPage's `redirect_uri ?? redirectURI ?? url`, only the third read is ever live, and the first two are spellings the route never answers. Untouched in this PR.",
"tests": "No code changed this round. The changeset gates ran at HEAD 95283f2 in the re-created worktree. node scripts/check-changeset-presence.mjs: exit 0, '12 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s): .changeset/12301-consent-public-client-name.md.' node scripts/check-changeset-no-major.mjs: exit 0, 'No changeset declares a `major` bump.' node scripts/check-changeset-fixed.mjs: exit 0, 'All workspace packages are in the changeset fixed group.' Check-runs on 95283f2 (total_count 32 = 32 listed): Changeset Declaration, Changeset Bump Policy, Changeset Fixed Group Check, Changeset Overwrite Report, Changeset Claim Re-read, Governed Surface Queue Guard and label are all completed/success. The other 25 check-runs were not read here. The round-0 code gates (build, type-check, the 268-file vitest run, the i18n gates, the 3 ablations) stand at 59470e8, and no tracked file outside .changeset/ differs between 59470e8 and 95283f2.",
"gates": [
{
"command": "node scripts/check-changeset-presence.mjs",
"verdict": "12 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s): .changeset/12301-consent-public-client-name.md.",
"exit": 0,
"head": "95283f2"
},
{
"command": "node scripts/check-changeset-no-major.mjs",
"verdict": "No changeset declares a `major` bump.",
"exit": 0,
"head": "95283f2"
},
{
"command": "node scripts/check-changeset-fixed.mjs",
"verdict": "All workspace packages are in the changeset fixed group.",
"exit": 0,
"head": "95283f2"
},
{
"command": "GET /repos/objectstack-ai/objectui/commits/95283f2…/check-runs (read)",
"verdict": "Changeset Declaration / Bump Policy / Fixed Group Check / Overwrite Report / Claim Re-read: completed success (re-triggered on the new head)",
"exit": 0,
"head": "95283f2"
}
],
"line_budget": "n/a: no skills/** in the diff",
"files_changed": [
".changeset/12301-consent-public-client-name.md (one line: @object-ui/i18n patch to minor)"
],
"deviations": [
"The worktree was re-created on the existing local branch, which equalled origin at 59470e8d9 (ls-remote checked), instead of a fresh -b from origin; the merge of origin/main was a no-op. No pnpm install was run, because the three changeset scripts need none. After the push, node_modules was absent and the worktree was removed (exit 0).",
"The vendor tarball @better-auth/oauth-provider@1.7.3 was re-fetched via npm pack into the session scratchpad for the read-only consent measurement, then deleted."
],
"mcp_calls": "0",
"api_writes": "1 relay write as objectstack-fleet[bot]: this os-dev-report comment, POST /repos/objectstack-ai/objectui/issues/12301/comments via post-stamped (dispatch POST /repos/objectstack-ai/objectstack/dispatches). One git push of 59470e8..95283f2 (not REST). No label writes. No PR body PATCH; none is needed, because the PR body names no semver level. The stale \"## Gates, all at `59470e8`\" heading is the seat's to edit if it wants.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed — measured this round (see summary): OAuthConsentPage's consent POST reads `redirect_uri ?? redirectURI ?? url`. The served answer, per the vendor runtime and the SDK's OAuthConsentResult, carries only `url`, so the first two reads are dead spellings and the flow works through `url`. Whether that alias chain is carded under Commandment 0.1 is the seat's call. Dedupe words: consent redirect_uri redirectURI url alias OAuthConsentResult",
"carrier: none · noted, not filed (unchanged from round 0) — polish: oauth.consent.unknownApp opens the title in lower case in Latin-script packs"
]
}
Reviewed against GitHub and the diff, not the reports. Inputs: claim 6139050988, reports 6139566902 (round 0) and 6139800905 (patch round 1).
Shape:
Draft on main. The first line is Fixes #12301, and no other card number sits beside a closing keyword.
Clause-②: yes is line-initial, as the claim declared.
The 13 files are exactly the claimed surface: the page, one new co-located test, the ten locale packs and the changeset.
check-governed-merges --pr reads NOT governed, 338 changed lines.
The fix, read against the diff:
OAuthConsentPage asks GET /api/v1/auth/oauth2/public-client?client_id=…, the URL @objectstack/client's oauth.applications.getPublic builds, once a user is signed in, with credentials: 'include' kept.
It names the client by a non-empty client_name and shows logo_uri.
Any non-answer shows the role=alert could-not-identify block, with the raw id under "Client ID" in a code node. The id never stands in the name's place.
The old name / icon / envelope reads are gone. ⛔ No server change, no route, no export.
Two pack keys in all ten packs: oauth.consent.unidentified, oauth.consent.clientId.
Triage's pins (6138546687): registered → name; unknown client_id → could-not-identify; the request URL is pinned to the SDK's own, so a regression to the dead path goes red. The dev's three ablation legs each went red as pre-registered, with restore proven.
Contract review:
FAIL 6139743891 on head 59470e8, on ② only: @object-ui/i18n was levelled patch for a published widening.
Patch round 1 moved that one line to minor. The compare shows one file, +1/-1, and blob-identical code.
PASS 6139860545 on head 95283f2.
CI: on 59470e8, 30 success and 3 skipped. On 95283f2, the changeset, lint, build and governed checks are green, and the test shards, Type Check, Repo Gates and Spec Main Shape Gate are still running. Landing waits for every check on this head.
Acceptance notes:
The consent POST reads its redirect as redirect_uri ?? redirectURI ?? url. The patch round measured, read-only, that the served answer carries only url: the vendor's handleRedirect JSON arm, and @objectstack/client's OAuthConsentResult { redirect: true; url: string }. So the first two reads are spellings the route never answers. The flow works through url, so this is dead code, not a reachable defect, and under the filing gate it is not a card. carrier: the next card that touches this page's consent POST.
apps/console/package.json declares @objectstack/client^17.3.0 as a devDependency. The page's new type import, OAuthApplicationPublic, first ships in 17.4.0. The lockfile resolves 17.7.0, and the import is type-only, so the build is unaffected. carrier: the next console pin or dependency bump.
oauth.consent.unknownApp ("an application") opens the title in lower case in Latin-script packs. The title now shows while the lookup is in flight and when it gives no name. Polish, outside the three classes. carrier: none.
Landing: ready, then auto-merge once mergeable_state settles, through the merge queue.
The merge: through the merge queue (added_to_merge_queue 2026-10-13T06:50Z, merged 2026-10-13T07:02Z), a squash onto 093d55b. Fixes #12301 closed this card completed.
Landed by content, not by sha:
All 13 files of the PR on mainca45c6d are blob-identical to the accepted head 95283f2. The changeset, the page, the new test and all ten locale packs were compared one by one.
That head is covered by ACCEPT 6139872968 and contract review PASS 6139860545.
OAuthConsentPage.tsx on main asks oauth2/public-client?client_id=, and oauth2/applications/ no longer appears in it.
Publishing: a merge to objectui main deploys nothing. The change ships with the maintainer's next chore: release packages, as @object-ui/i18nminor and @object-ui/consolepatch (one fixed group).
This card:pm:dispatched comes off with this note. The Acceptance notes in 6139872968 stand, with no carrier filed.
Activity
objectstack-fleet commentedon Oct 13, 2026
Triage: first grade,
bug·priority:p2·domain:ui·area:identity, intopm:queue. The consent page reads the route the server serves. ⛔ No new server routeTriage seat (seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-13T04:55Z. ⛔ Not a claim, ⛔ not a dispatch.client_idtakes away the one thing the user needs to recognise it. The flow still works, and nothing is granted without consent.GET /api/v1/auth/oauth2/public-client(better-authoauth.applications.getPublic,requires: 'oidcProvider') is in@objectstack/plugin-auth@17.7.0's route ledger, read at that tag now. objectui's^17reaches it today.OAuthConsentPagelooks up the client through that route, with its declared parameter, or through the SDK method if objectui's client exposes it./oauth2/applications/:id/publicroute is added on the server.client_idshows the could-not-identify state.apps/console/src/pages/auth/OAuthConsentPage.tsx(domain:ui); rationale: name the application the user is consenting to.objectstack-fleet commentedon Oct 13, 2026
Claim: PM loop round 1 (as triage
6138546687directs: the consent page names the application through the route the server serves,GET /api/v1/auth/oauth2/public-client; a lookup that does not answer shows a could-not-identify state with the id, never a fallback that looks like a name; ⛔ no new server route)Session:
session_01RZYixBnXPWvcm2YenWo6eJAccount:
os-support-ai(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-12301-consent-public-client-nameWorktree:
objectui-issue-12301Domain:
domain:uiSeat:
domain:ui#2File surface, on objectui
mainf0532d8:apps/console/src/pages/auth/OAuthConsentPage.tsx, the client lookup effect (:65–:68,fetch(`${AUTH_BASE}/oauth2/applications/${encodeURIComponent(clientId)}/public`, …)).@object-ui/auth), only if it already wraps the public-client lookup. The dev measures whether it does.packages/i18n/src/locales/*.ts, all ten packs, only for the could-not-identify state, and only if no existing key says it truly..changeset/12301-consent-public-client-name.md(@object-ui/console;@object-ui/i18nif touched; ⛔ nevermajor).Container & model:
M,mode:subagent,model: default tier — dispatch-gates --tier --repo objectstack-ai/objectui: "no path-derived mandate"Clause-②: yes
Responsibility:
objectui's console OAuth consent page (apps/console OAuthConsentPage), which asks /oauth2/applications/ID/public — a route the server does not mount — and on its 404 names the application by its raw client_id | the server already serves GET /api/v1/auth/oauth2/public-client (better-auth oauth.applications.getPublic, in @objectstack/plugin-auth 17.7.0's route ledger, per triage 6138546687) | every user who reaches the consent screen of an oidcProvider deployment (observed in objectstack-ai/objectstack#23104's pass on the stock showcase)Thread-read: 6138546687
Serial constraints cleared: read at 2026-10-13T05:34Z on objectui
mainf0532d8:apps/console/src/pages/auth/(4 open PRs read: objectui#12309 grid saves, two dependabotnextbumps, the release PR objectui#11600). The last change under that folder onmainis08b5d97d1(the timezone-prompt module rename), not this page.AppManagementPage.tsx) and objectui#12302 (therecord:related_liststrict face) share no file. This seat's objectui#12288 (console/marketplace/) and objectui#12285 (AppContent.tsx,data-objectstackprobeAppAccess) share none, except possibly the ten locale packs: ordinary concurrency, the later lander resolves it.Clause-②: yesis declared up front for the likely pack key. If the dev measures an existing key that says it truly, the report narrows it tonoand says why.Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
os-dev-report
{ "issue": 12301, "status": "done", "branch": "claude/issue-12301-consent-public-client-name", "pr": "https://github.com/objectstack-ai/objectui/pull/12312", "session": "session_01RZYixBnXPWvcm2YenWo6eJ — mode:subagent, the parent (PM) id named by claim 6139050988; identity bit is the branch", "premise_still_valid": true, "summary": "OAuthConsentPage now looks the client up at GET /api/v1/auth/oauth2/public-client?client_id=… (the served route, the URL @objectstack/client's oauth.applications.getPublic builds), only once a session exists (the vendor route is sessionMiddleware), with the page's existing credentials: 'include'. It reads client_name and logo_uri. The name/icon/data-envelope reads are dropped, because the route answers none of them. On any non-answer (non-2xx incl. the vendor's 404 not_found for an unknown or disabled client, a network failure, or a 2xx without client_name), it shows a role=alert could-not-identify block: 'This application could not be identified.' plus the raw id under a 'Client ID' label in a CODE node. The id is never in the name's place: the title names 'an application', as it also does while the lookup is in flight. Two keys were added in all ten packs (oauth.consent.unidentified, oauth.consent.clientId), so Clause-② stays yes. Zone 2: H1 confirmed (query param client_id; answer fields client_name/logo_uri). H2 measured (session-required; credentials reused; lookup gated on signed-in user). H3 falsified (@object-ui/auth does not wrap it; no export added; the SDK does, but the consent route renders outside ConnectedShell, so no client instance exists there; the test pins the page URL to the real SDK's). H4 falsified (no existing key says it).", "tests": "All at HEAD 59470e8. New file apps/console/src/pages/auth/__tests__/OAuthConsentPage.public-client-12301.test.tsx: 'Test Files 1 passed (1) / Tests 8 passed (8)'. Pins: (c) the only lookup URL equals the real @objectstack/client getPublic URL, with an encoded client_id and credentials include; (a) client_name titles the page and logo_uri is shown, with no alert and no id; (b) 404 not_found / 500 / network / 200-without-client_name each show the alert with 'Client ID' and the id exactly once, as a CODE node inside the alert; plus in-flight (no name, no id) and signed-out (zero lookups). Full run: pnpm exec vitest run --maxWorkers=2 apps/console/ packages/i18n/ packages/app-shell/src/__tests__/product-neutral-renderer.ratchet.test.ts gave 'Test Files 268 passed (268) / Tests 3430 passed | 13 skipped (3443)', 'VERDICT command-exit 0' (shared-box seconds). Ablations on the committed fix, each via objectstack scripts/ablation-replace.mjs (anchor 1 to 0, blob changed; restore proven by blob == HEAD and git diff HEAD empty), no build leg (console tests import the page source by relative path): URL back to the old path gave 'Tests 2 failed | 6 passed'; '?? clientId' back in the name fallback gave 'Tests 5 failed | 3 passed'; session gate removed gave 'Tests 3 failed | 5 passed'. All three went red, the expected direction.", "gates": [ { "command": "pnpm --workspace-concurrency=2 --filter '@object-ui/console^...' build", "verdict": "os-verify-lock: VERDICT command-exit 0 (35 packages; contains the i18n closure)", "exit": 0, "head": "59470e8" }, { "command": "pnpm --filter @object-ui/console type-check && pnpm --filter @object-ui/i18n type-check", "verdict": "os-verify-lock: VERDICT command-exit 0 (both script names echoed; tsc --listFiles shows the new test is in the console program)", "exit": 0, "head": "59470e8" }, { "command": "pnpm exec vitest run --maxWorkers=2 apps/console/ packages/i18n/ packages/app-shell/src/__tests__/product-neutral-renderer.ratchet.test.ts", "verdict": "Test Files 268 passed (268) / Tests 3430 passed | 13 skipped (3443) / VERDICT command-exit 0", "exit": 0, "head": "59470e8" }, { "command": "node scripts/check-changeset-presence.mjs", "verdict": "12 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)", "exit": 0, "head": "59470e8" }, { "command": "pnpm check:i18n-keys", "verdict": "Every in-scope call-site key resolves against the en pack (3361 keys) …", "exit": 0, "head": "59470e8" }, { "command": "pnpm check:i18n-drift", "verdict": "0 en value(s) changed (2 key(s) added, 0 removed …)", "exit": 0, "head": "59470e8" }, { "command": "pnpm check:i18n-dead-keys", "verdict": "report-only; no oauth.* candidate listed", "exit": 0, "head": "59470e8" }, { "command": "pnpm check:control-bytes", "verdict": "check-control-bytes: OK (scanned 8592 tracked text file(s); skipped 85 binary)", "exit": 0, "head": "59470e8" }, { "command": "pnpm check:new-line-citations", "verdict": "VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0", "exit": 0, "head": "59470e8" }, { "command": "pnpm exec eslint --no-inline-config --format json (the 12 changed source files)", "verdict": "declared narrowing: 12 files, 0 errors, 1 warning (react-hooks/immutability on the logo onError style.display line, the same single warning the base version of the page carries); population: eslint --print-config resolves a config for all 12; invariance: parserOptions.project and projectService null, no custom rule reads files", "exit": 0, "head": "59470e8" } ], "line_budget": "n/a: no skills/** in the diff", "files_changed": [ ".changeset/12301-consent-public-client-name.md", "apps/console/src/pages/auth/OAuthConsentPage.tsx", "apps/console/src/pages/auth/__tests__/OAuthConsentPage.public-client-12301.test.tsx", "packages/i18n/src/locales/{ar,de,en,es,fr,ja,ko,pt,ru,zh}.ts (two keys each; en also has its block comment updated)" ], "deviations": [ "Citation form: Zone 2 asked for file:line on a named sha. objectui AGENTS.md Commandment 11 bans line addresses in issue and PR bodies, so the PR body cites by symbol and quoted text on named versions instead: objectstack e540e7ffb auth-route-ledger.ts row 'GET /api/v1/auth/oauth2/public-client'; @better-auth/oauth-provider@1.7.3 dist/authorize-9whjxVLJ.mjs getOAuthClientPublic / getClientPublicEndpoint / schemaToOAuth; @objectstack/client@17.7.0 oauth.applications.getPublic.", "Vendor source: neither checkout has @better-auth/oauth-provider installed (objectstack has no node_modules; objectui does not depend on it), so its 1.7.3 tarball (the version plugin-auth pins) was read via npm pack into the session scratchpad.", "The vitest run widens the touched dirs by one file, product-neutral-renderer.ratchet.test.ts, which reads locale keys.", "ESLint ran on the changed files only (declared narrowing, Gates item 7); repo-wide pnpm lint is left to CI.", "Commit trailers use the model-free pair from objectui AGENTS.md, not the harness's model-named Co-Authored-By line. The PR footer uses os-dev's session-URL form." ], "mcp_calls": "0", "api_writes": "2 relay writes as objectstack-fleet[bot] via POST /repos/objectstack-ai/objectstack/dispatches. (1) pr_create, executing POST /repos/objectstack-ai/objectui/pulls (draft) and POST /repos/objectstack-ai/objectui/issues/12312/assignees; read-back reported 8539 bytes sent and stored, identical, and assignee os-support-ai. (2) This os-dev-report comment, POST /repos/objectstack-ai/objectui/issues/12301/comments, via post-stamped. Zero label writes: the PR labels apps, tests and package: i18n come from labeler.yml. git push x2 is not REST.", "open_questions": [], "out_of_scope_findings": [ "carrier: none · noted, not filed (Acceptance notes) — polish: oauth.consent.unknownApp ('an application') opens the title and request sentences, so in Latin-script packs the in-flight / could-not-identify title starts in lower case; the string is unchanged by this PR", "carrier: none · noted, not filed (Acceptance notes) — observation, unmeasured: the same page's consent POST reads the redirect through an alias chain (redirect_uri, then redirectURI, then url); the vendor consent answer shape was not measured and the code is untouched" ], "cleanup": "node_modules removed and git worktree remove /home/user/objectui-issue-12301 exited 0 (the tree was clean). No dev server or monitor was started. Both background jobs (closure build, vitest run) were run under the lock and waited on in the foreground with tail --pid until they exited." }Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
os-dev-report
{ "issue": 12301, "round": "patch round 1 (contract review 6139743891 FAIL on semver only)", "status": "done", "branch": "claude/issue-12301-consent-public-client-name", "pr": "https://github.com/objectstack-ai/objectui/pull/12312", "head": "95283f2", "session": "session_01RZYixBnXPWvcm2YenWo6eJ — mode:subagent, the parent (PM) id named by claim 6139050988; identity bit is the branch", "premise_still_valid": true, "summary": "Fixed the semver FAIL: .changeset/12301-consent-public-client-name.md now declares '@object-ui/i18n': minor, and '@object-ui/console' stays patch. No other word of the changeset body contradicted minor; the body never names a level, so it is unchanged. Commit 95283f2 is on top of 59470e8, on a worktree re-created from the branch. origin/main was still f0532d8a8, so merging it was a no-op (Already up to date; merge-base --is-ancestor f0532d8a8 HEAD gave exit 0). Pushed with no force. The PR body names no semver level: grep for patch, minor and semver had 0 hits, while the control strings 'Clause-②: yes' and the PR body's closing line for this card had 1 hit each. So no PR-body sentence contradicts minor. One stale header: the PR body's '## Gates, all at `59470e8`' heading predates this head. Code is byte-identical since then; only the changeset frontmatter moved, and its three changeset gates were re-run at 95283f2 (below). Read-only measurement of the consent POST, with no code change: the served answer carries `url` only (with `redirect: true`); it never carries `redirect_uri` or `redirectURI`. Evidence: objectstack e540e7ffb auth-route-ledger.ts row `{ route: 'POST /api/v1/auth/oauth2/consent', family: 'oauth-provider', source: 'better-auth', disposition: 'sdk', client: 'oauth.consent', requires: 'oidcProvider' }`. In @better-auth/oauth-provider@1.7.3 dist/authorize-9whjxVLJ.mjs, `oauth2Consent: createAuthEndpoint(\"/oauth2/consent\", …)` returns `consentEndpoint(ctx, opts, runOAuth2Authorize)`. Its deny branch returns `{ redirect: true, url: formatErrorURL(query.get(\"redirect_uri\") ?? \"\", \"access_denied\", \"User denied access\", …) }`. Its accept branch sets `ctx?.headers?.set(\"accept\", \"application/json\")` and returns `await authorize(ctx, …)`. Every authorize exit, including `redirectWithAuthorizationCode`'s `return handleRedirect(ctx, redirectUriWithCode.toString())` and `authorizeRedirectOnError`, goes through `handleRedirect`, whose JSON arm is `if (fromFetch || acceptJson) return { redirect: true, url: uri.toString() }`. `redirect_uri` appears there only as the openapi metadata's declared response property (`properties: { redirect_uri: … }, required: [\"redirect_uri\"]`), which the runtime never returns. `redirectURI` occurs 3 times in that file, all as a local variable in the end-session URI helper, never as a response key. The SDK agrees: objectstack e540e7ffb packages/client/src/index.ts `export interface OAuthConsentResult { redirect: true; url: string; }`, documented 'for BOTH decisions … read `url`'. So in OAuthConsentPage's `redirect_uri ?? redirectURI ?? url`, only the third read is ever live, and the first two are spellings the route never answers. Untouched in this PR.", "tests": "No code changed this round. The changeset gates ran at HEAD 95283f2 in the re-created worktree. node scripts/check-changeset-presence.mjs: exit 0, '12 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s): .changeset/12301-consent-public-client-name.md.' node scripts/check-changeset-no-major.mjs: exit 0, 'No changeset declares a `major` bump.' node scripts/check-changeset-fixed.mjs: exit 0, 'All workspace packages are in the changeset fixed group.' Check-runs on 95283f2 (total_count 32 = 32 listed): Changeset Declaration, Changeset Bump Policy, Changeset Fixed Group Check, Changeset Overwrite Report, Changeset Claim Re-read, Governed Surface Queue Guard and label are all completed/success. The other 25 check-runs were not read here. The round-0 code gates (build, type-check, the 268-file vitest run, the i18n gates, the 3 ablations) stand at 59470e8, and no tracked file outside .changeset/ differs between 59470e8 and 95283f2.", "gates": [ { "command": "node scripts/check-changeset-presence.mjs", "verdict": "12 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s): .changeset/12301-consent-public-client-name.md.", "exit": 0, "head": "95283f2" }, { "command": "node scripts/check-changeset-no-major.mjs", "verdict": "No changeset declares a `major` bump.", "exit": 0, "head": "95283f2" }, { "command": "node scripts/check-changeset-fixed.mjs", "verdict": "All workspace packages are in the changeset fixed group.", "exit": 0, "head": "95283f2" }, { "command": "GET /repos/objectstack-ai/objectui/commits/95283f2…/check-runs (read)", "verdict": "Changeset Declaration / Bump Policy / Fixed Group Check / Overwrite Report / Claim Re-read: completed success (re-triggered on the new head)", "exit": 0, "head": "95283f2" } ], "line_budget": "n/a: no skills/** in the diff", "files_changed": [ ".changeset/12301-consent-public-client-name.md (one line: @object-ui/i18n patch to minor)" ], "deviations": [ "The worktree was re-created on the existing local branch, which equalled origin at 59470e8d9 (ls-remote checked), instead of a fresh -b from origin; the merge of origin/main was a no-op. No pnpm install was run, because the three changeset scripts need none. After the push, node_modules was absent and the worktree was removed (exit 0).", "The vendor tarball @better-auth/oauth-provider@1.7.3 was re-fetched via npm pack into the session scratchpad for the read-only consent measurement, then deleted." ], "mcp_calls": "0", "api_writes": "1 relay write as objectstack-fleet[bot]: this os-dev-report comment, POST /repos/objectstack-ai/objectui/issues/12301/comments via post-stamped (dispatch POST /repos/objectstack-ai/objectstack/dispatches). One git push of 59470e8..95283f2 (not REST). No label writes. No PR body PATCH; none is needed, because the PR body names no semver level. The stale \"## Gates, all at `59470e8`\" heading is the seat's to edit if it wants.", "open_questions": [], "out_of_scope_findings": [ "carrier: none · noted, not filed — measured this round (see summary): OAuthConsentPage's consent POST reads `redirect_uri ?? redirectURI ?? url`. The served answer, per the vendor runtime and the SDK's OAuthConsentResult, carries only `url`, so the first two reads are dead spellings and the flow works through `url`. Whether that alias chain is carded under Commandment 0.1 is the seat's call. Dedupe words: consent redirect_uri redirectURI url alias OAuthConsentResult", "carrier: none · noted, not filed (unchanged from round 0) — polish: oauth.consent.unknownApp opens the title in lower case in Latin-script packs" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
ACCEPT: PR objectui#12312 (head
95283f2) ·domain:uiseat 2 ·session_01RZYixBnXPWvcm2YenWo6eJ· 2026-10-13T06:40ZReviewed against GitHub and the diff, not the reports. Inputs: claim
6139050988, reports6139566902(round 0) and6139800905(patch round 1).main. The first line isFixes #12301, and no other card number sits beside a closing keyword.Clause-②: yesis line-initial, as the claim declared.check-governed-merges --prreads NOT governed, 338 changed lines.OAuthConsentPageasksGET /api/v1/auth/oauth2/public-client?client_id=…, the URL@objectstack/client'soauth.applications.getPublicbuilds, once a user is signed in, withcredentials: 'include'kept.client_nameand showslogo_uri.role=alertcould-not-identify block, with the raw id under "Client ID" in a code node. The id never stands in the name's place.name/icon/ envelope reads are gone. ⛔ No server change, no route, no export.oauth.consent.unidentified,oauth.consent.clientId.6138546687): registered → name; unknownclient_id→ could-not-identify; the request URL is pinned to the SDK's own, so a regression to the dead path goes red. The dev's three ablation legs each went red as pre-registered, with restore proven.6139743891on head59470e8, on ② only:@object-ui/i18nwas levelledpatchfor a published widening.minor. The compare shows one file, +1/-1, and blob-identical code.6139860545on head95283f2.59470e8, 30 success and 3 skipped. On95283f2, the changeset, lint, build and governed checks are green, and the test shards,Type Check,Repo GatesandSpec Main Shape Gateare still running. Landing waits for every check on this head.redirect_uri ?? redirectURI ?? url. The patch round measured, read-only, that the served answer carries onlyurl: the vendor'shandleRedirectJSON arm, and@objectstack/client'sOAuthConsentResult { redirect: true; url: string }. So the first two reads are spellings the route never answers. The flow works throughurl, so this is dead code, not a reachable defect, and under the filing gate it is not a card.carrier:the next card that touches this page's consent POST.apps/console/package.jsondeclares@objectstack/client^17.3.0as a devDependency. The page's new type import,OAuthApplicationPublic, first ships in 17.4.0. The lockfile resolves 17.7.0, and the import is type-only, so the build is unaffected.carrier:the next console pin or dependency bump.oauth.consent.unknownApp("an application") opens the title in lower case in Latin-script packs. The title now shows while the lookup is in flight and when it gives no name. Polish, outside the three classes.carrier:none.mergeable_statesettles, through the merge queue.Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
Landed: PR objectui#12312 →
mainca45c6d·domain:uiseat 2 ·session_01RZYixBnXPWvcm2YenWo6eJ· 2026-10-13T07:03Zadded_to_merge_queue2026-10-13T06:50Z, merged 2026-10-13T07:02Z), a squash onto093d55b.Fixes #12301closed this cardcompleted.mainca45c6dare blob-identical to the accepted head95283f2. The changeset, the page, the new test and all ten locale packs were compared one by one.6139872968and contract review PASS6139860545.OAuthConsentPage.tsxonmainasksoauth2/public-client?client_id=, andoauth2/applications/no longer appears in it.maindeploys nothing. The change ships with the maintainer's nextchore: release packages, as@object-ui/i18nminorand@object-ui/consolepatch(one fixed group).pm:dispatchedcomes off with this note. The Acceptance notes in6139872968stand, with no carrier filed.Generated by Claude Code
fix(console): the OAuth consent page names the client through the ser…