fix(auth): actionable cloud-runtime sign-in failure message + docs (#3025) (#4649)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mega Mind
2026-07-07 13:58:34 -07:00
committed by GitHub
co-authored by Claude Opus 4.8
parent ed79bf489c
commit 208b51d5d2
3 changed files with 102 additions and 1 deletions
@@ -10,6 +10,7 @@ import {
} from '../../store/deepLinkAuthState';
import { getStoredCoreMode } from '../configPersistence';
import {
authStoreFailureUserMessage,
classifyAuthStoreFailure,
registerAuthDeepLinkState,
setupDesktopDeepLinkListener,
@@ -396,3 +397,36 @@ describe('classifyAuthStoreFailure', () => {
expect(classifyAuthStoreFailure(bare)).not.toBe('other');
});
});
describe('authStoreFailureUserMessage (issue #3025)', () => {
const CLOUD_KINDS = [
'auth_me_timeout',
'auth_me_unauthorized',
'auth_me_gateway',
'network',
'other',
];
// Local / unset mode always keeps the plain retry message — the failure there
// is a transient embedded-core/backend blip that retrying can clear.
it.each(['local', null] as const)('stays generic for mode=%s', mode => {
for (const kind of CLOUD_KINDS) {
expect(authStoreFailureUserMessage(kind, mode)).toBe('Sign-in failed. Please try again.');
}
});
it('points cloud-mode users at the remote runtime, not a dead-end retry', () => {
for (const kind of CLOUD_KINDS) {
const msg = authStoreFailureUserMessage(kind, 'cloud');
expect(msg).not.toBe('Sign-in failed. Please try again.');
expect(msg.toLowerCase()).toContain('remote');
}
});
it('gives kind-specific cloud hints (401 token, gateway/timeout BACKEND_URL)', () => {
expect(authStoreFailureUserMessage('auth_me_unauthorized', 'cloud')).toContain('RPC token');
expect(authStoreFailureUserMessage('auth_me_gateway', 'cloud')).toContain('BACKEND_URL');
expect(authStoreFailureUserMessage('auth_me_timeout', 'cloud')).toContain('BACKEND_URL');
expect(authStoreFailureUserMessage('network', 'cloud')).toContain('online');
});
});
+43 -1
View File
@@ -285,7 +285,7 @@ const handleAuthDeepLink = async (parsed: URL, requireStateNonce = true) => {
fingerprint: ['deep-link-auth', 'session-store-failed', kind],
});
console.warn('[DeepLink][auth] session store failed — staying on signin (kind=%s)', kind);
failDeepLinkAuthProcessing('Sign-in failed. Please try again.');
failDeepLinkAuthProcessing(authStoreFailureUserMessage(kind, getStoredCoreMode()));
}
}
};
@@ -321,6 +321,48 @@ export const classifyAuthStoreFailure = (message: string): string => {
return 'other';
};
/**
* Build the user-facing message for an auth-*store* failure (issue #3025).
*
* `auth_store_session` makes the core validate the freshly minted session token
* against the backend `GET /auth/me` before persisting it. In **cloud mode**
* that validation runs on the user's *remote* `openhuman-core`, so the dominant
* failure is the remote runtime being unable to reach/authenticate against the
* backend (misconfigured `BACKEND_URL`, offline, or an older core that predates
* `allowPendingBackendValidation`) — not a problem the desktop can retry away.
* The old blanket "Sign-in failed. Please try again." gave cloud users no path
* forward; point them at the remote runtime instead. Local mode keeps the plain
* retry message (a transient embedded-core/backend blip that retrying can fix).
*/
export const authStoreFailureUserMessage = (
kind: string,
mode: 'local' | 'cloud' | null
): string => {
if (mode !== 'cloud') {
return 'Sign-in failed. Please try again.';
}
switch (kind) {
case 'auth_me_unauthorized':
return (
'Your remote (cloud) runtime rejected the sign-in. Check the RPC token in ' +
'Settings and that the runtime points at the correct backend, then try again.'
);
case 'auth_me_timeout':
case 'auth_me_gateway':
case 'network':
return (
'Your remote (cloud) runtime could not reach the backend to finish sign-in. ' +
'Check that the runtime is online and its BACKEND_URL is configured, then try again.'
);
default:
return (
'Your remote (cloud) runtime could not complete sign-in. Verify it is running ' +
'and configured correctly (RPC URL/token in Settings, backend connectivity), ' +
'then try again.'
);
}
};
/**
* Handle `openhuman://payment/success?session_id=...` deep links.
* Fired when a Stripe checkout session completes and the browser redirects
+25
View File
@@ -399,6 +399,31 @@ Restart the desktop app. The provider chain in `App.tsx` will route all RPC
calls to the remote core; nothing else changes. Public `http://` hosts are
rejected by the app picker; use HTTPS for any publicly reachable core.
### Troubleshooting: sign-in fails after OAuth on a cloud runtime
Symptom: OAuth in the browser completes ("close this and return to the app"),
but the desktop then shows a sign-in error — while the **same** account signs in
fine on the local (embedded) runtime (issue #3025).
Root cause is almost always the **remote** core, not the desktop. `auth_store_session`
makes the core validate the fresh session token against the backend
(`GET /auth/me`) before persisting it; on a cloud runtime that call runs on your
server, so it fails if the remote core can't reach/authenticate the backend:
- **`BACKEND_URL` unset or wrong.** It is required (see the env table above) and
must be `https://api.tinyhumans.ai` for prod (or the staging URL). A missing
value is the most common cause — one reporter's failure was exactly this.
- **Backend unreachable from the server** (egress firewall, DNS, TLS interception)
→ the core sees a gateway/timeout on `/auth/me`.
- **Outdated core.** Older cores predate `allowPendingBackendValidation` and
validate synchronously with no grace; update the server to a current release.
- **Wrong RPC token.** A token mismatch surfaces as HTTP 401 on `/rpc`; re-paste
the token (see "Rotating the bearer token").
Check the remote core logs for `Session validation failed (GET /auth/me)` and the
status/reason. The desktop now reports a cloud-specific, actionable message for
these instead of a generic "try again" (issue #3025).
---
## Named-volume ownership and the Docker entrypoint