mihomo-party-org/clash-party · warning · Error
Login timed out
Error message
Login timed out
What it means
browserLogin starts a local HTTP server and opens a browser for an OAuth authorization-code flow; a setTimeout rejects the promise with 'Login timed out' when the callback with the authorization code does not arrive within timeoutMs. This aborts the pending login so the app does not wait forever on a server that may never receive a redirect.
Source
Thrown at src/main/resolve/plugin/oauth.ts:51
const open = opts.open ?? ((u: string): Promise<void> => shell.openExternal(u))
const timeoutMs = opts.timeoutMs ?? CALLBACK_TIMEOUT_MS
const p = new Promise<OAuthResult>((resolve, reject) => {
const server = http.createServer()
let settled = false
const finish = (fn: () => void): void => {
if (settled) return
settled = true
clearTimeout(timer)
server.close()
fn()
}
const timer = setTimeout(() => finish(() => reject(new Error('Login timed out'))), timeoutMs)
server.on('request', (req, res) => {
const reqUrl = new URL(req.url ?? '/', 'http://127.0.0.1')
if (reqUrl.pathname !== '/callback') {
res.writeHead(404)
res.end()
return
}
const code = reqUrl.searchParams.get('code')
const retState = reqUrl.searchParams.get('state')
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' })
if (!code || retState !== state) {
res.end('<html><body>Login failed. You may close this window.</body></html>')
finish(() => reject(new Error('Invalid OAuth callback (state mismatch or missing code)')))
return
}
res.end('<html><body>Login complete. You may close this window.</body></html>')
const addr = server.address()
const port = typeof addr === 'object' && addr ? addr.port : 0
finish(() => resolve({ code, verifier, redirectUri: `http://127.0.0.1:${port}/callback` }))
})
server.on('error', (e) => finish(() => reject(e)))
server.listen(0, '127.0.0.1', () => {View on GitHub (pinned to 911e090537)
Solutions
- Retry the login (call the login command again) and complete the flow promptly in the opened browser window.
- Ensure a default browser is available/openable in the environment, or copy the auth URL and open it manually on another machine — make sure the redirect goes to the same host running the app.
- Increase the timeout value if flows routinely need longer.
- Check firewall/proxy rules so 127.0.0.1 callback requests are not blocked.
Example fix
// before
await oauth() // user misses the window, rejects after timeoutMs
// after
try {
await oauth()
} catch (e) {
if (e.message === 'Login timed out') {
notifyUser('Login window expired — click Login to try again')
await oauth()
} else throw e
} Defensive patterns
Strategy: try-catch
Validate before calling
// Before starting OAuth, check the environment can complete the flow:
if (!process.defaultApp && (process.platform === 'linux' && !process.env.DISPLAY)) {
console.warn('No GUI available — open the printed auth URL manually on a machine that can reach this host')
} Try / catch
try {
await browserLogin()
} catch (e) {
if (e instanceof Error && e.message === 'Login timed out') {
// prompt user to retry; optionally increase timeoutMs and re-invoke
await retryWithBackoff(() => browserLogin(), { retries: 2 })
} else {
throw e
}
} Prevention
- Complete the OAuth flow promptly once the browser window opens.
- Increase timeoutMs if the flow legitimately needs more time.
- Ensure a default browser is configured and 127.0.0.1 callback traffic is not blocked by firewall/proxy.
- On headless machines, use SSH port forwarding or copy the auth URL to a browser that can reach the callback host.
When it happens
Trigger: The user closes the browser window/tab before completing login; the browser never opens (headless/SSH environment, no default browser configured); the OAuth provider page errors or the user abandons the flow; network blocking redirects back to http://127.0.0.1:<port>/callback within the timeout.
Common situations: Running the app on a remote machine without a GUI; corporate proxy/firewall blocking the loopback callback; user multi-tasking and letting the login page sit until it expires; very short timeoutMs configuration.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Get device failed
- Core PID ${proc.pid ?? 'unknown'} is still running after SIG
- Request failed with status ${res.status}: ${url}
- Invalid latest.yml from update source
- unreachable
AI-assisted analysis of mihomo-party-org/clash-party@911e090537 (2026-08-30).
Data as JSON: /api/errors/6b2264a2352bcdc9.
Report an issue: GitHub.