paperclipai/paperclip · error · Error

Approval request failed.

Error message

Approval request failed.

What it means

Thrown by requestInstallApproval() when POST /api/companies/{id}/approvals returns null/undefined. This is the second stage of the 403-fallback flow: the install was denied, the CLI tried to create a board approval request instead, and the server returned no approval object. The CLI refuses to report success without an approval record.

Source

Thrown at cli/src/commands/client/teams.ts:425

        `A Paperclip CLI agent-run attempted to install catalog team "${trimmedRef}" into company "${ctx.companyId}", ` +
        `but the API denied the install with: ${error.message}.`,
      recommendedAction:
        "Approve the catalog team source and rerun the install with a board or agent-creator token, or grant agents:create to the requesting agent and rerun the same command.",
      risks: [
        "Catalog team installation can create agents, projects, tasks, routines, skills, and secret bindings.",
        "Only approve after checking the catalog source, selected files, target manager, and collision strategy.",
      ],
      installAttempt: {
        companyId: ctx.companyId,
        catalogRef: trimmedRef,
        options: approvalInstallOptions,
        deniedReason: error.message,
      },
    },
  };
  const approval = await ctx.api.post<Approval>(apiPath`/api/companies/${ctx.companyId}/approvals`, payload);
  if (!approval) {
    throw new Error("Approval request failed.");
  }
  return {
    status: "approval_requested",
    approval,
    installAttempt: {
      companyId: ctx.companyId,
      catalogRef: trimmedRef,
      options: returnedInstallOptions,
      deniedReason: error.message,
    },
  };
}

function omitInstallSecretValues(options: CatalogTeamInstallOptions): CatalogTeamInstallOptions {
  if (!options.secretValues) return options;
  const { secretValues: _secretValues, ...safeOptions } = options;
  return safeOptions;
}

View on GitHub (pinned to 67001ec6eb)

Solutions

  1. Check CLI/server version alignment for the approvals contract.
  2. Retry the install; if the 403 was transient, the fallback may not be needed.
  3. Inspect the raw approval POST response (status + body) with curl to see what the server returns.

Example fix

# diagnose the fallback contract
curl -sS -X POST "$API/api/companies/$CID/approvals" \
  -H "authorization: Bearer $TOKEN" -H "content-type: application/json" \
  -d '{"type":"request_board_approval","issueIds":[],"payload":{"title":"t","summary":"s","instructions":[],"installAttempt":{"companyId":"'$CID'","catalogRef":"ref","options":{},"deniedReason":"x"}}}' \
  | jq .
Defensive patterns

Strategy: try-catch

Type guard

function isApproval(v: unknown): v is Approval {
  return v != null && typeof v === "object" && "id" in (v as object);
}

Try / catch

try {
  await run(["teams", "install", ref, "--request-approval-on-forbidden"]);
} catch (err) {
  if (err instanceof Error && /Approval request failed/.test(err.message)) {
    console.error("Approval endpoint returned no body. Check server version / contract.");
    process.exit(2);
  }
  throw err;
}

Prevention

When it happens

Trigger: `teams install` denied with 403 (agents:create) → fallback approval POST returns empty 2xx body, 204, or the route is missing/disabled. Also possible if the approvals endpoint itself errors in a way that resolves to null.

Common situations: Version skew (CLI expects an Approval body, server returns 204/no-content), permissions issue on approval creation, or a proxy stripping the response body.

Related errors


AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12). Data as JSON: /api/errors/b5d2427ba135f43d. Report an issue: GitHub.