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

  1. Re-issue a fresh approval for this company/issue and use the new id in the runner tool call.
  2. Verify the approval id belongs to the same company as the run binding (query the approvals table for companyId).
  3. Check whether the approval was deleted or expired and request a new one through the normal approval flow.
  4. 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

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


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