paperclipai/paperclip · error · Error
CLI auth challenge expired before approval.
Error message
CLI auth challenge expired before approval.
What it means
Thrown by loginBoardCli() when the polled challenge status is `expired`. The challenge has a server-issued expiresAt; if the poll returns status === "expired" before it is approved, the CLI aborts immediately rather than waiting out its own clock. Same user-facing message as [3] but reached via the server's explicit expired status.
Source
Thrown at cli/src/client/board-auth.ts:281
);
setStoredBoardCredential({
apiBase,
token: challenge.boardApiToken,
userId: me.userId ?? me.user?.id ?? null,
storePath: params.storePath,
});
return {
token: challenge.boardApiToken,
approvalUrl,
userId: me.userId ?? me.user?.id ?? null,
};
}
if (status.status === "cancelled") {
throw new Error("CLI auth challenge was cancelled.");
}
if (status.status === "expired") {
throw new Error("CLI auth challenge expired before approval.");
}
await sleep(pollMs);
}
throw new Error("CLI auth challenge expired before approval.");
}
export async function revokeStoredBoardCredential(params: {
apiBase: string;
token: string;
}): Promise<void> {
const apiBase = normalizeApiBase(params.apiBase);
await requestJson<{ revoked: boolean }>(`${apiBase}/api/cli-auth/revoke-current`, {
method: "POST",
headers: {
authorization: `Bearer ${params.token}`,
},View on GitHub (pinned to 67001ec6eb)
Solutions
- Re-run `paperclipai login` and approve the printed URL promptly.
- If the browser did not open automatically, copy the URL from stderr into a browser manually.
- Check for clock skew between the CLI host and the server (`date` on both); correct NTP if drift is large.
- If TTL is too short for your workflow, ask the operator to raise the server-side challenge expiry.
Defensive patterns
Strategy: retry
Try / catch
async function loginWithRetry(params: Parameters<typeof loginBoardCli>[0], attempts = 2) {
for (let i = 0; i < attempts; i++) {
try {
return await loginBoardCli(params);
} catch (err) {
const msg = err instanceof Error ? err.message : '';
if (msg.includes('expired before approval') && i < attempts - 1) {
console.error('Challenge expired, retrying...');
continue;
}
throw err;
}
}
throw new Error('unreachable');
} Prevention
- Approve the printed URL immediately after running login.
- Sync system clocks via NTP on both CLI host and server.
- If TTL is short, ask the operator to raise the server challenge expiry.
- Ensure PAPERCLIP_NO_BROWSER is unset so the browser opens automatically.
When it happens
Trigger: The approving user did not open/approve the URL before the challenge TTL elapsed, and the server marked it expired. The poll then observes status === "expired" and throws. Clock skew between client and server can also make the server report expired while the client thinks time remains.
Common situations: User walked away after `paperclipai login`. Approval page never loaded (browser did not open — see PAPERCLIP_NO_BROWSER). Server challenge TTL shortened by config. Significant client/server clock drift.
Related errors
- Request failed: ${response.status}
- CLI auth challenge was cancelled.
- Environment variable ${envName} is empty or not set.
- Challenge secret is required. Pass --token or --token-env.
- Import request returned no data.
AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12).
Data as JSON: /api/errors/02f8f31da585ca5a.
Report an issue: GitHub.