paperclipai/paperclip · error

GitHub inventory failed: installation token was missing

Error message

GitHub inventory failed: installation token was missing

What it means

Thrown by listGitHubInstallationRepositories when the GitHub App installation-token exchange succeeded at HTTP level but the response body contains no 'token' field. The installation token is required for all subsequent /installation/repositories page fetches, so without it the inventory cannot proceed.

Source

Thrown at server/src/services/chat-provider-inventory.ts:189

  appJwt: string;
  installationId: string;
  fetch: typeof globalThis.fetch;
}): Promise<ChatProviderInventoryResult> {
  const headers = {
    accept: "application/vnd.github+json",
    authorization: `Bearer ${input.appJwt}`,
    "x-github-api-version": "2022-11-28",
  };
  const tokenResponse = await input.fetch(
    `https://api.github.com/app/installations/${encodeURIComponent(input.installationId)}/access_tokens`,
    { method: "POST", headers, signal: githubRequestSignal() },
  );
  const tokenBody = await jsonResponse<{ token?: string; message?: string }>(
    tokenResponse,
    "GitHub",
  );
  if (!tokenBody.token)
    throw new Error("GitHub inventory failed: installation token was missing");

  const resources: ChatProviderResourceInventoryItem[] = [];
  let page = 1;
  try {
    while (true) {
      const url = new URL("https://api.github.com/installation/repositories");
      url.searchParams.set("per_page", "100");
      url.searchParams.set("page", String(page));
      const response = await input.fetch(url, {
        headers: {
          accept: "application/vnd.github+json",
          authorization: `Bearer ${tokenBody.token}`,
          "x-github-api-version": "2022-11-28",
        },
        signal: githubRequestSignal(),
      });
      const body = await jsonResponse<{
        repositories?: Array<{

View on GitHub (pinned to 01ad858492)

Solutions

  1. Retry the repository inventory; a transient malformed token response usually resolves on retry.
  2. Reconnect the GitHub App connection to restart the auth flow with fresh App credentials.
  3. Confirm the App is not suspended on GitHub (suspended apps cannot mint installation tokens).
  4. Bypass or inspect any proxy between the server and api.github.com for body rewriting.
Defensive patterns

Strategy: retry

Validate before calling

// Before inventory, confirm the App can mint tokens (not suspended):
const inst = await gh.request('GET /app/installations');
if (!inst.data.some(i => !i.suspended_at)) {
  throw new Error('No active installation; install/reactivate the App first');
}

Type guard

function hasInstallationToken(b: { token?: string; message?: string }): b is { token: string; message?: string } {
  return typeof b.token === 'string' && b.token.length > 0;
}

Try / catch

try { const repos = await listGitHubInstallationRepositories(input); }
catch (e) {
  if (e.message.includes('installation token was missing')) {
    await retryWithBackoff(() => listGitHubInstallationRepositories(input), 2);
    // if persistent, force reconnect to restart token exchange
  }
}

Prevention

When it happens

Trigger: The token-access response (POST /app/installations/{id}/access_tokens) parses OK but tokenBody.token is undefined — e.g. GitHub returned an unexpected shape, an error envelope with only 'message', or a proxied/mutated response.

Common situations: GitHub API partial outage returning an error envelope with 200; proxy rewriting the response; App suspended between auth steps; response contract drift from an API gateway in front of the server.

Related errors


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/859f31dc27420850. Report an issue: GitHub.