paperclipai/paperclip · error
paperclip_runner_approval_not_found
paperclip_runner_approval_not_found
Error message
paperclip_runner_approval_not_found
What it means
PaperclipRunnerToolAuthority.#approval loads an approval record by id via approvalService(db).getById(id) and throws paperclip_runner_approval_not_found when no row exists or the row belongs to a different company than the run binding. This enforces company scoping: a run may only resolve approval gates that belong to its own company.
Source
Thrown at server/src/services/native-runtime/paperclip-runner-tool-authority.ts:350
const handle = await openRunnerApiWorkspaceFile(resolved.realPath);
try {
const stat = await handle.stat();
if (!stat.isFile() || stat.size > RUNNER_API_MAX_BYTES) throw badRequest("Workspace file exceeds API transfer limit");
const bytes = Buffer.alloc(RUNNER_API_MAX_BYTES + 1);
let length = 0;
while (length < bytes.length) {
const { bytesRead } = await handle.read(bytes, length, bytes.length - length, length);
if (!bytesRead) break;
length += bytesRead;
}
if (length > RUNNER_API_MAX_BYTES) throw badRequest("Workspace file exceeds API transfer limit");
return { bytes: bytes.subarray(0, length), filename: basename(resolved.realPath), contentType: "application/octet-stream" };
} finally { await handle.close(); }
}
async #approval(id: string) {
const approval = await approvalService(this.db).getById(id);
if (!approval || approval.companyId !== this.binding.companyId) throw new Error("paperclip_runner_approval_not_found");
return approval;
}
async #boundContext() {
const [row] = await this.db.select({ issue: issues, actor: agents, run: heartbeatRuns })
.from(heartbeatRuns)
.innerJoin(issues, eq(issues.id, this.binding.issueId))
.innerJoin(agents, eq(agents.id, this.binding.agentId))
.where(and(
eq(heartbeatRuns.id, this.binding.runId),
eq(heartbeatRuns.companyId, this.binding.companyId),
eq(heartbeatRuns.agentId, this.binding.agentId),
eq(heartbeatRuns.nativeIssueId, this.binding.issueId),
eq(issues.companyId, this.binding.companyId),
eq(issues.assigneeAgentId, this.binding.agentId),
eq(issues.executionRunId, this.binding.runId),
eq(agents.companyId, this.binding.companyId),
))View on GitHub (pinned to 01ad858492)
Solutions
- Re-issue a fresh approval for this company/issue and use the new id in the runner tool call.
- Verify the approval id belongs to the same company as the run binding (query the approvals table for companyId).
- Check whether the approval was deleted or expired and request a new one through the normal approval flow.
- If the id comes from an earlier message/tool result, re-fetch current pending approvals instead of reusing cached ids.
Example fix
// before
await authority.approval("appr_from_other_company");
// after
const pending = await listApprovals({ companyId: binding.companyId, issueId });
await authority.approval(pending[0].id); Defensive patterns
Strategy: validation
Validate before calling
const approval = await approvalService(db).getById(id);
if (!approval || approval.companyId !== binding.companyId) {
throw new Error("approval is missing or belongs to another company — request a fresh approval for this run");
} Type guard
function isOwnCompanyApproval(a: { companyId: string } | null | undefined, companyId: string): a is { companyId: string } {
return !!a && a.companyId === companyId;
} Try / catch
try {
const approval = await authority.approval(id);
} catch (err) {
if (err instanceof Error && err.message === "paperclip_runner_approval_not_found") {
// list current pending approvals for this company/issue and use a fresh id
} else throw err;
} Prevention
- Always source approval ids from a live listing of pending approvals, never from cached tool transcripts.
- Never reuse approval ids across companies or issues.
- Treat approvals as single-use/expiring and re-request after revocation or expiry.
When it happens
Trigger: A runner tool call references an approval id that (a) does not exist in the approvals table, (b) was deleted/expired, or (c) exists under a different companyId than this.binding.companyId.
Common situations: Agent passes a stale or fabricated approval id; cross-company id replay (id copied from another company's run); approval was revoked between issuance and the tool call; typo/truncation of the id by the calling agent.
Understand the failure class
Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.
Related errors
- Unable to resolve company for plugin API route
- Select an organization to test adapter environment
- run_not_found
- evaluation_issue_not_found
- Paperclip run authentication is unavailable
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/5bd2b0a0fce00d79.
Report an issue: GitHub.