Skip to content

fix(web): login auto-logout race + magic-link disabled fallback + hub /healthz alias#188

Merged
finedesignz merged 1 commit into
mainfrom
fix/healthz-liveness-alias
May 30, 2026
Merged

fix(web): login auto-logout race + magic-link disabled fallback + hub /healthz alias#188
finedesignz merged 1 commit into
mainfrom
fix/healthz-liveness-alias

Conversation

@finedesignz
Copy link
Copy Markdown
Owner

Why

User locked out of https://app.remo-code.com — "can't login". A HAR capture showed POST /api/auth/login 200GET /api/profile 200POST /api/auth/logout 200, all at the same timestamp: a successful login immediately followed by an auto-logout.

Fixes

  1. Auto-logout race (root cause). useProfile's loading flag was never reset to true on the token null→set transition at sign-in. It stayed at the stale false from the no-token branch, so for one render App.tsx's dead-credential cleanup effect saw !profileLoading && !profile && token and fired signOut(). Regression from the fix(web): blank screen after login/auth expiry → show Login #180–182 auth hardening; only bites the legacy password path (the only working path under Titanium bypass). Fix: setLoading(true) at the start of fetchProfile.
  2. Magic-link disabled fallback. Under TITANIUM_BYPASS the request-link endpoint 503s titanium_disabled; the UI swallowed it and showed a fake "check your inbox". requestMagicLink now throws magic_link_disabled on that config-wide (non-enumerating) state and Login.tsx routes to the password form.
  3. /healthz alias. Coolify probe hits /healthz; hub only served /health + bearer-gated /healthz/deep. Added a plain liveness alias.

Verification

  • tsc -b && vite build clean.
  • Post-deploy: probe /healthz 200, then re-run the login HAR flow and confirm no auto-logout.

🤖 Generated with Claude Code

…k; hub /healthz alias

Three prod-access fixes (user locked out of app.remo-code.com):

1. useProfile auto-logout race (PRIMARY — "can't login"): the `loading` flag
   was never reset to true when `token` transitions null→set on sign-in, so it
   stayed at the stale `false` left by the no-token branch. For one render after
   a successful legacy password login, App.tsx's dead-credential cleanup effect
   saw `!profileLoading && !profile && token` and immediately fired signOut() —
   logging the user straight back out. HAR confirmed: POST /api/auth/login 200 +
   GET /api/profile 200 + POST /api/auth/logout all at the same timestamp.
   Fix: setLoading(true) at the start of fetchProfile.

2. Magic-link disabled UX: under TITANIUM_BYPASS the request-link endpoint 503s
   `titanium_disabled`, but the UI swallowed it and showed "check your inbox" for
   an email that never sends. requestMagicLink now throws `magic_link_disabled`
   on that specific (config-wide, non-enumerating) state and Login.tsx routes the
   user to the password form. The only working auth path under bypass.

3. /healthz liveness alias: the Coolify probe hits /healthz; the hub only served
   /health and bearer-gated /healthz/deep. Added a plain /healthz alias.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@finedesignz finedesignz merged commit b878ae7 into main May 30, 2026
3 checks passed
finedesignz added a commit that referenced this pull request May 30, 2026
Fix A — slash menu: register the bot's command list with Telegram via
setMyCommands at bridge startup so typing `/` shows the command popup.
BOT_COMMANDS in commands.ts is the single source of truth for the menu +
/help (list/session/status/doctor/help — only the real handled commands).
Idempotent, fire-and-forget, gated on botToken.

Fix B — repo navigator: the callback_query session-picker path was
structurally intact at #188 (proven by new integration tests) — its real-
world "broken" symptom was the missing slash menu (Fix A) making /list
undiscoverable. Hardened: webhook picker re-render now uses PAGE_SIZE
instead of a hardcoded 20, and set_session/paginate/unknown callbacks are
now covered by integration tests that lock the contract.

Fix C — inline approval prompts: when a Telegram-driven session hits a
tool permission prompt it no longer blocks silently. ws/agent.ts emits a
permission_request:pending event (new isolated bus, mirrors assistant-
events); the bridge surfaces it inline with Approve/Deny buttons to users
whose default session matches. Taps route through the existing callback_query
path → handlePermissionCallback forwards a permission_response frame on the
agent socket (same frame the web client sends). Pending prompts are tracked
in an in-memory registry keyed by request_id (callback_data is too small for
session+request UUIDs) which also enforces per-user authorization. No new
dispatch path — this is a control-frame reply to a runner-initiated prompt,
so the cost-cap pipeline is untouched.

Tests: telegram-approvals (registry + codec), telegram-bridge (slash menu +
permission surfacing), telegram-webhook (callback set_session/paginate +
approve/deny/stale/foreign/offline). check-baseline GREEN (947 pass, 0 fail).
docs/telegram-bridge.md updated in lockstep.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
finedesignz added a commit that referenced this pull request May 30, 2026
…advanced) (#190)

App.tsx routes purely off window.location.hash. The legacy password flow
calls signIn() to set auth state but — unlike magic-link, which redirects
via /auth/callback → / — never changes the hash. So route stays 'login'
and App's `route === 'login'` gate (added in #188 to harden against stale
tokens) re-renders <Login> forever despite valid token + cookie + profile.
Symptom: clicking Sign in does nothing.

Fix: signIn() navigates to #/ when on the login route. Magic-link and deep
links unaffected (guarded on hash).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant