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
- Check CLI/server version alignment for the approvals contract.
- Retry the install; if the 403 was transient, the fallback may not be needed.
- 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
- Keep CLI and server versions aligned for the approvals contract.
- Retry the install directly if the 403 may have been transient.
- Inspect the raw approval POST to confirm the response shape.
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
- No update status returned for skill ${skill.key}.
- No audit result returned for skill ${skill.key}.
- Catalog team not found: ${catalogRef}
- Invalid --secret-value "${raw}". Use key=value.
- ${name} must be a JSON object
AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12).
Data as JSON: /api/errors/b5d2427ba135f43d.
Report an issue: GitHub.