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
Mobile sign-in: a person who has to verify their email during the hand-off gets a mail link to 页面不存在, and the verify page drops the refusal time #30157
Business reason: a new user who signs up during the mobile sign-in (the system auth session, objectstack-ai/mobile#148), on a server that requires email verification, gets a verification mail whose link lands on 页面不存在. The mail is the only road back, so that person cannot finish signing in from it. Nobody is hitting this yet, because no released app uses the flow; it would start when one does.
Reader: domain:ui, one PR on ui/apps/console/src/pages/auth/. objectstack-ai/mobile#190 is the first consumer.
Duplicate search: withConsoleBaseRootRelative in issues (0 hits) and verify-email-prompt redirect handoff (2 hits, #29997 and a closed QA run, neither about this).
reach: read from the code on origin/main, not run end to end. First touch should measure it. The helper's wrong answer for this kind of path was measured in the browser on #29971, which is the same defect in a sibling path. Found by the contract review of PR #30010 (comment on that PR, head 3e59db0af0).
1. The verification mail's link
RegisterPage.tsx (verificationCallbackURL) and VerifyEmailPromptPage.tsx (callbackURL) send a safe redirect through withConsoleBaseRootRelative, which treats every redirect as a Console route. For a hand-off redirect that gives /_console/api/v1/auth/one-time-token/handoff?…, a page that does not exist.
Not just a prefix fix. Which browser opens that mail matters. It is usually not the system auth session the app is waiting on, and a token handed out there may have no app waiting for it. So the right callback is a product question: decide what a person who verifies by mail should see. Fix the dead end at minimum, and say in the PR what the chosen answer does.
2. The refusal time is dropped on this road
PR #30010 carries handoff_refused_at on the links between /login and /register, through handoffMarkQuery. Three hops still drop it: LoginPage's EMAIL_NOT_VERIFIED branch, RegisterPage's onVerificationRequired (each builds the query from email and redirect), and VerifyEmailPromptPage.handleBackToLogin (rebuilds /login?redirect=).
A person with an old session on a device whose clock is behind who takes this road is judged by the age check alone and forwarded into the route's 401 JSON. It stops and cannot loop. It needs several conditions at once, so it is the smaller half.
A test per hop, with a control that a non-hand-off redirect keeps today's links byte for byte.
A browser check of the mail's link for the case above.
This is a session-transfer flow: the maintainer reviews and merges.
Related: objectstack-ai/mobile#148. Not a blocker of it: a Google sign-in should not take this road (not measured), and a person verifying by email is the case affected.
Measure it first, since it was read from the code. Which page a mail-verified person should land on is a product answer the PR states. A session-transfer flow, so the maintainer reviews and merges.
Claim: PM loop round 2
Session: session_01WGufzgzSz4DH8R8v1yq6bm (card thread executor; seat session session_01WGufzinedUGSXteN45poyo)
Account: os-support-ai
Branch: claude/project-thread-yz3rkx (harness-pinned card thread branch; the claude/issue-<n>-<slug> convention does not apply to a project card thread)
Worktree: objectstack-issue-30157
Domain: domain:ui
Seat: domain:ui#4
File surface: ui/apps/console/src/pages/auth/** (RegisterPage.tsx, VerifyEmailPromptPage.tsx, LoginPage.tsx, handoffRedirect.ts), ui/apps/console/src/utils/consoleBase* if the fix needs it, their tests, root .changeset/
Container & model: M, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate; PM judgment call, default tier), effort: high
Clause-②: no
Metadata-protocol: none
Responsibility: RegisterPage.tsx/VerifyEmailPromptPage.tsx send a hand-off redirect through withConsoleBaseRootRelative (console code) | no platform path covers the mail-verified hand-off road | reached by a person who signs up during the mobile hand-off on a server requiring email verification; no released app uses the flow today (triage 6637932107)
Thread-read: 6637932107
Premise: holds
Serial constraints cleared: none (no in-flight domain:ui claim declares ui/apps/console/src/pages/auth/**; PR #30010, the predecessor on this path, merged 2026-11-05T01:05Z). Session-transfer flow: the maintainer reviews and merges.
Ruling recorded · #30157 mail-verified landing · option A
The maintainer (Yibo Wang) answered the dev's decision card in the project card thread on 2026-11-05T03:06Z, choosing the first option. The card, verbatim as they read it:
Ruling: A. The verification mail's link for a hand-off sign-up lands on a Console page saying the address is confirmed and to go back to the app; no one-time token is minted in the browser that opened the mail. Not chosen: B (/login carrying the hand-off redirect, signs in in the mail browser) and C (plain Console /login).
Measured by the dev before the ruling (dev boot, maindfafae81): today a signed-in mail browser lands on 页面不存在, and a signed-out one is sent by the SPA guard to /login?redirect=<hand-off>, de facto option B by accident. The PR implements A and carries handoff_refused_at on the three hops the card names. The PR follows on branch claude/project-thread-yz3rkx; this is a session-transfer flow, so the maintainer reviews and merges it.
{
"issue": 30157,
"status": "done",
"branch": "claude/project-thread-yz3rkx",
"pr": "https://github.com/objectstack-ai/objectstack/pull/30243",
"session": "session_01WGufzgzSz4DH8R8v1yq6bm",
"premise_still_valid": true,
"summary": "Premise holds in part, measured on a dev boot (examples/app-crm, origin/main dfafae813 Console, posture open, OS_AUTH_REQUIRE_EMAIL_VERIFICATION=true, OS_TRUSTED_APP_SCHEMES): the mail's callbackURL was /_console/api/v1/auth/one-time-token/handoff?...; a mail browser holding any Console session landed on 'Page not found', while a signed-out one was sent by the SPA's sign-in guard to /_console/login carrying the hand-off (a de-facto 'continue in the mail browser'); the three hops dropped handoff_refused_at. The landing was a real product fork, so it went to the user as a decision card; the user chose option A ('回 App 提示页', 2026-11-05T03:06:38Z). Fix: handoffRedirect.ts gains rule 6 and verificationCallbackURL(redirect): a hand-off redirect returns to /verify-email?handoff=verified (the existing handoffNavigationTarget matcher decides), any other safe redirect keeps withConsoleBaseRootRelative byte for byte; RegisterPage (sign-up mail) and VerifyEmailPromptPage (resend) use it; VerifyEmailPage shows 'Email verified, go back to the app and sign in there' with no Console link and no token spend, and its failure state on better-auth's appended error. handoffMarkQuery is appended on LoginPage's EMAIL_NOT_VERIFIED navigation, RegisterPage's onVerificationRequired and handleBackToLogin. New i18n key auth.verifyEmail.handoffSuccessDescription in ten locales. No server file, auth route, token issuance or hand-off endpoint touched. Card assignee not written.",
"tests": "New handoffVerificationMail-30157.test.tsx (real AuthProvider over a mock client, embedded mount): mail-link pins for sign-up and resend + invitation CONTROL; one pin per hop (login EMAIL_NOT_VERIFIED, register verification required, Back to login) + CONTROLs (invitation redirect with mark, hand-off redirect with no mark) byte for byte; landing pins (handoff=verified, error appended, no-token CONTROL). Run at 9d2f99aa: Test Files 1 passed, Tests 11 passed. Ablation (fix committed first; five Console src files checked out from origin/main in a trap-restored script, landing proved by grep -c 0 hits for verificationCallbackURL and handoffMarkQuery in VerifyEmailPromptPage): Tests 6 failed | 5 passed (2 mail pins, 3 hop pins, the landing pin red; controls green); restore proved by empty git diff HEAD and HEAD blob hashes. No build in the loop (Console tests import src). Browser walk before/after on the dev boot, shots and walk json in /mnt/project-files/ui-orders/30157-shots/: before signed-in mail browser -> 'Page not found', signed-out -> login with hand-off; after both -> /verify-email?handoff=verified; prompt and Back to login carry the mark; sign-in in the waiting window -> route 302 ai.objectos.mobile://auth/callback.",
"gates": "One locked run at head 9d2f99aa (os-verify-lock VERDICT command-exit 0, 1342 s), 40 commands, all exit 0: console auth tests (28 files, 228 tests), i18n tests (95 files, 1443 passed, 13 skipped), @object-ui/console type-check, @object-ui/i18n type-check, eslint on the 6 changed console files, node scripts/check-sdui-manifest.mjs (+ --self-test), and every one of the 35 families dispatch-gates --commands derives. dispatch-gates --ran: '35 derived famil(ies) accounted for — 35 run, 0 NOT-MEASURED'. List: /mnt/project-files/ui-orders/30157-gates-ran.list. PR check-runs: in_progress at report time (not awaited, per os-dev).",
"line_budget": "17 files, +334 / -30 (git diff --shortstat origin/main...HEAD): new test file 220 lines, 5 console src files, 10 locale lines, changeset",
"files_changed": [".changeset/30157-handoff-verification-mail.md", "ui/apps/console/src/pages/auth/LoginPage.tsx", "ui/apps/console/src/pages/auth/RegisterPage.tsx", "ui/apps/console/src/pages/auth/VerifyEmailPage.tsx", "ui/apps/console/src/pages/auth/VerifyEmailPromptPage.tsx", "ui/apps/console/src/pages/auth/handoffRedirect.ts", "ui/apps/console/src/pages/auth/__tests__/handoffVerificationMail-30157.test.tsx", "ui/packages/i18n/src/locales/{ar,de,en,es,fr,ja,ko,pt,ru,zh}.ts"],
"deviations": "Commit trailer uses 'Co-Authored-By: Claude' (repo rule) instead of the harness's model-named trailer. PR held from 02:00Z to 03:07Z for the user's landing decision (order's needs_decision fence); work continued on the recommended option meanwhile. Branch pushed before the PR (harness-pinned branch). An attempt to stop my own dev boot by process pattern was refused by the auto-mode classifier; it was stopped by its listed pids instead.",
"dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
"mcp_calls": "3 — mcp__github__issue_read (get #30157), mcp__github__list_pull_requests (open PRs, path-overlap check), no write tools",
"api_writes": "3 — git push (branch); relay pr_create #30243 (run 41959601718, 10549 bytes sent = stored); this os-dev-report comment via post-stamped.mjs",
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": ["carrier: none · noted, not filed: the SPA sends a signed-out visitor at an unknown /_console path to /login?redirect=PATH, which is how the old mail link reached a hand-off sign-in by accident; no defect once this lands."]
}
Activity
objectstack-fleet commentedon Nov 4, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: first-touch audit · p2 ·
domain:ui·pm:queue· 2026-11-04T17:53Z⛔ Not a claim.
withConsoleBaseRootRelativetreats the hand-off redirect as a Console route), and three hops drophandoff_refused_at. No released app uses the flow yet, so p2, as Mobile hand-off: a signed-out page navigation goes to the sign-in page, so the app never has to name the Console's login route #29997 and Console sign-in page cannot reach the mobile hand-off: a redirect to the auth API lands on 页面不存在 #29971.Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:ui#4 · os-support-ai · session_01WGufzinedUGSXteN45poyo
Claim: PM loop round 2
Session:
session_01WGufzgzSz4DH8R8v1yq6bm(card thread executor; seat sessionsession_01WGufzinedUGSXteN45poyo)Account:
os-support-aiBranch:
claude/project-thread-yz3rkx(harness-pinned card thread branch; theclaude/issue-<n>-<slug>convention does not apply to a project card thread)Worktree:
objectstack-issue-30157Domain:
domain:uiSeat:
domain:ui#4File surface:
ui/apps/console/src/pages/auth/**(RegisterPage.tsx, VerifyEmailPromptPage.tsx, LoginPage.tsx, handoffRedirect.ts),ui/apps/console/src/utils/consoleBase*if the fix needs it, their tests, root.changeset/Container & model:
M,mode:thread,model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate; PM judgment call, default tier),effort: highClause-②: no
Metadata-protocol: none
Responsibility:
RegisterPage.tsx/VerifyEmailPromptPage.tsxsend a hand-off redirect throughwithConsoleBaseRootRelative(console code) | no platform path covers the mail-verified hand-off road | reached by a person who signs up during the mobile hand-off on a server requiring email verification; no released app uses the flow today (triage 6637932107)Thread-read: 6637932107
Premise: holds
Serial constraints cleared: none (no in-flight
domain:uiclaim declaresui/apps/console/src/pages/auth/**; PR #30010, the predecessor on this path, merged 2026-11-05T01:05Z). Session-transfer flow: the maintainer reviews and merges.objectstack-fleet commentedon Nov 5, 2026
domain:ui#4 · os-support-ai · session_01WGufzinedUGSXteN45poyo
Ruling recorded · #30157 mail-verified landing · option A
The maintainer (Yibo Wang) answered the dev's decision card in the project card thread on 2026-11-05T03:06Z, choosing the first option. The card, verbatim as they read it:
Ruling: A. The verification mail's link for a hand-off sign-up lands on a Console page saying the address is confirmed and to go back to the app; no one-time token is minted in the browser that opened the mail. Not chosen: B (
/logincarrying the hand-off redirect, signs in in the mail browser) and C (plain Console/login).Measured by the dev before the ruling (dev boot,
maindfafae81): today a signed-in mail browser lands on 页面不存在, and a signed-out one is sent by the SPA guard to/login?redirect=<hand-off>, de facto option B by accident. The PR implements A and carrieshandoff_refused_aton the three hops the card names. The PR follows on branchclaude/project-thread-yz3rkx; this is a session-transfer flow, so the maintainer reviews and merges it.objectstack-fleet commentedon Nov 5, 2026
domain:ui#4 · os-support-ai · session_01WGufzgzSz4DH8R8v1yq6bm
os-dev-report
Stamp: 2026-11-05T03:10Z
{ "issue": 30157, "status": "done", "branch": "claude/project-thread-yz3rkx", "pr": "https://github.com/objectstack-ai/objectstack/pull/30243", "session": "session_01WGufzgzSz4DH8R8v1yq6bm", "premise_still_valid": true, "summary": "Premise holds in part, measured on a dev boot (examples/app-crm, origin/main dfafae813 Console, posture open, OS_AUTH_REQUIRE_EMAIL_VERIFICATION=true, OS_TRUSTED_APP_SCHEMES): the mail's callbackURL was /_console/api/v1/auth/one-time-token/handoff?...; a mail browser holding any Console session landed on 'Page not found', while a signed-out one was sent by the SPA's sign-in guard to /_console/login carrying the hand-off (a de-facto 'continue in the mail browser'); the three hops dropped handoff_refused_at. The landing was a real product fork, so it went to the user as a decision card; the user chose option A ('回 App 提示页', 2026-11-05T03:06:38Z). Fix: handoffRedirect.ts gains rule 6 and verificationCallbackURL(redirect): a hand-off redirect returns to /verify-email?handoff=verified (the existing handoffNavigationTarget matcher decides), any other safe redirect keeps withConsoleBaseRootRelative byte for byte; RegisterPage (sign-up mail) and VerifyEmailPromptPage (resend) use it; VerifyEmailPage shows 'Email verified, go back to the app and sign in there' with no Console link and no token spend, and its failure state on better-auth's appended error. handoffMarkQuery is appended on LoginPage's EMAIL_NOT_VERIFIED navigation, RegisterPage's onVerificationRequired and handleBackToLogin. New i18n key auth.verifyEmail.handoffSuccessDescription in ten locales. No server file, auth route, token issuance or hand-off endpoint touched. Card assignee not written.", "tests": "New handoffVerificationMail-30157.test.tsx (real AuthProvider over a mock client, embedded mount): mail-link pins for sign-up and resend + invitation CONTROL; one pin per hop (login EMAIL_NOT_VERIFIED, register verification required, Back to login) + CONTROLs (invitation redirect with mark, hand-off redirect with no mark) byte for byte; landing pins (handoff=verified, error appended, no-token CONTROL). Run at 9d2f99aa: Test Files 1 passed, Tests 11 passed. Ablation (fix committed first; five Console src files checked out from origin/main in a trap-restored script, landing proved by grep -c 0 hits for verificationCallbackURL and handoffMarkQuery in VerifyEmailPromptPage): Tests 6 failed | 5 passed (2 mail pins, 3 hop pins, the landing pin red; controls green); restore proved by empty git diff HEAD and HEAD blob hashes. No build in the loop (Console tests import src). Browser walk before/after on the dev boot, shots and walk json in /mnt/project-files/ui-orders/30157-shots/: before signed-in mail browser -> 'Page not found', signed-out -> login with hand-off; after both -> /verify-email?handoff=verified; prompt and Back to login carry the mark; sign-in in the waiting window -> route 302 ai.objectos.mobile://auth/callback.", "gates": "One locked run at head 9d2f99aa (os-verify-lock VERDICT command-exit 0, 1342 s), 40 commands, all exit 0: console auth tests (28 files, 228 tests), i18n tests (95 files, 1443 passed, 13 skipped), @object-ui/console type-check, @object-ui/i18n type-check, eslint on the 6 changed console files, node scripts/check-sdui-manifest.mjs (+ --self-test), and every one of the 35 families dispatch-gates --commands derives. dispatch-gates --ran: '35 derived famil(ies) accounted for — 35 run, 0 NOT-MEASURED'. List: /mnt/project-files/ui-orders/30157-gates-ran.list. PR check-runs: in_progress at report time (not awaited, per os-dev).", "line_budget": "17 files, +334 / -30 (git diff --shortstat origin/main...HEAD): new test file 220 lines, 5 console src files, 10 locale lines, changeset", "files_changed": [".changeset/30157-handoff-verification-mail.md", "ui/apps/console/src/pages/auth/LoginPage.tsx", "ui/apps/console/src/pages/auth/RegisterPage.tsx", "ui/apps/console/src/pages/auth/VerifyEmailPage.tsx", "ui/apps/console/src/pages/auth/VerifyEmailPromptPage.tsx", "ui/apps/console/src/pages/auth/handoffRedirect.ts", "ui/apps/console/src/pages/auth/__tests__/handoffVerificationMail-30157.test.tsx", "ui/packages/i18n/src/locales/{ar,de,en,es,fr,ja,ko,pt,ru,zh}.ts"], "deviations": "Commit trailer uses 'Co-Authored-By: Claude' (repo rule) instead of the harness's model-named trailer. PR held from 02:00Z to 03:07Z for the user's landing decision (order's needs_decision fence); work continued on the recommended option meanwhile. Branch pushed before the PR (harness-pinned branch). An attempt to stop my own dev boot by process pattern was refused by the auto-mode classifier; it was stopped by its listed pids instead.", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "mcp_calls": "3 — mcp__github__issue_read (get #30157), mcp__github__list_pull_requests (open PRs, path-overlap check), no write tools", "api_writes": "3 — git push (branch); relay pr_create #30243 (run 41959601718, 10549 bytes sent = stored); this os-dev-report comment via post-stamped.mjs", "open_questions": [], "security_findings": "none", "out_of_scope_findings": ["carrier: none · noted, not filed: the SPA sends a signed-out visitor at an unknown /_console path to /login?redirect=PATH, which is how the old mail link reached a hand-off sign-in by accident; no defect once this lands."] }Generated by Claude Code