paperclipai/paperclip · error
Missing login
Error message
Missing login
What it means
For the anthropic provider, readVerifiedLocalAiCredential looks for a Claude login token on disk (or, when a suffixed loginHome is given, an isolated Keychain item). If no token is found it throws 'Missing login' — the user has not completed the provider's local sign-in on this machine.
Solutions
- Run `claude auth login` on the machine running Paperclip, then retry Connect
- Confirm HOME/loginHome points at the profile that holds the Claude credentials
- For isolated logins, re-create the suffixed Keychain item by signing in for that connection
- Run the server under the same OS user that performed the login
Example fix
// before # server runs as svc user; login done as alice → token not found // after sudo -u svc claude auth login # then retry Connect
Defensive patterns
Strategy: try-catch
Validate before calling
import { existsSync } from "node:fs";
import path from "node:path";
const ready = existsSync(path.join(loginHome ?? os.homedir(), ".claude", ".credentials.json")) ||
existsSync(path.join(loginHome ?? os.homedir(), ".claude.json"));
if (!ready) console.error("Run `claude auth login` before connecting"); Try / catch
try {
await readVerifiedLocalAiCredential({ provider: "anthropic", loginHome });
} catch (e) {
if (e instanceof UnprocessableError || /Could not verify the local subscription/.test(String(e?.message))) {
showSetupHint("Run: claude auth login");
} else throw e;
} Prevention
- Run provider logins on the same machine and OS user as the Paperclip server
- Verify HOME/loginHome matches the profile used for sign-in
- Pre-flight the login state in setup docs/scripts before calling Connect
- For isolated (suffixed) logins, create each Keychain item explicitly
When it happens
Trigger: provider='anthropic' with no Claude credentials file present and (for suffixed homes) no isolated Keychain token; readClaudeToken/readIsolatedClaudeKeychainToken both return null.
Common situations: Fresh machine or CI container where `claude auth login` was never run; HOME differs between the login shell and the server process; using an isolated loginHome whose Keychain item was deleted.
Related errors
- No credential found. Set
- A safe, unique --revision is required
- ANTHROPIC_API_KEY is required in the CLI process environment
- --api-key-env must name a valid environment variable.
- --api-key-secret-id must be a UUID
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/9e58f6466ea7b540.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/local-ai-credentials.ts:36
// authenticated user's isolated login is missing or invalid.
let token: string | null = null;
if (loginHome) {
for (const name of [".credentials.json", "credentials.json"]) {
const raw = await readLocalAiCredentialFile(path.join(loginHome, name)).catch(() => null);
if (!raw) continue;
let parsed;
try { parsed = JSON.parse(raw); } catch { continue; }
const value = parsed?.claudeAiOauth?.accessToken;
if (typeof value === "string" && value.length) { token = value; break; }
}
// On macOS the CLI stores the isolated login in the auth home's own
// suffixed Keychain item rather than a credentials file. The helper
// never consults the unsuffixed operator item.
if (!token) token = await readIsolatedClaudeKeychainToken(loginHome);
} else {
token = await readClaudeToken({ allowKeychain: true });
}
if (!token) throw new Error("Missing login");
await fetchClaudeQuota(token);
return token;
}
if (provider === "openai") {
const auth = await readCodexAuthInfo(loginHome);
if (!auth?.accessToken || !auth.refreshToken || !auth.idToken) throw new Error("Missing login");
await fetchCodexQuota(auth.accessToken, auth.accountId);
return JSON.stringify({ tokens: { access_token: auth.accessToken, refresh_token: auth.refreshToken, id_token: auth.idToken, account_id: auth.accountId }, last_refresh: auth.lastRefresh });
}
const raw = await fs.readFile(path.join(loginHome!, "auth.json"), "utf8");
const payload = parseGrokAuthPayload(JSON.parse(raw));
if (!payload || !hasUsableGrokAuthValue(payload.value)) throw new Error("Missing login");
const response = await fetch("https://api.x.ai/v1/models", {
headers: { Authorization: `Bearer ${payload.value.key}` },
redirect: "error", signal: AbortSignal.timeout(15000),
});
await response.body?.cancel();
if (!response.ok) throw new Error("Invalid login");View on GitHub (pinned to 3f1d897a7c)