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

  1. Run `claude auth login` on the machine running Paperclip, then retry Connect
  2. Confirm HOME/loginHome points at the profile that holds the Claude credentials
  3. For isolated logins, re-create the suffixed Keychain item by signing in for that connection
  4. 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

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


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)