paperclipai/paperclip · error · Error

paperclip_runner_attachment_staging_count_denied

paperclip_runner_attachment_staging_count_denied

Error message

paperclip_runner_attachment_staging_count_denied

What it means

Thrown when the wake attachment selections captured in the run's contextSnapshot exceed MAX_NATIVE_STAGED_ATTACHMENTS. The handoff refuses to stage more attachments than the hard cap allows, protecting the local workspace staging directory from unbounded writes.

Solutions

  1. Reduce the number of selected attachments in the wake request to at most MAX_NATIVE_STAGED_ATTACHMENTS and re-issue the wake
  2. Inspect run.contextSnapshot's wake attachment selections and prune duplicates before re-triggering the run
  3. If the batch is legitimately large, split the work across multiple wake runs, each under the cap

Example fix

// before
await stageNativeRunnerWakeAttachments({ db, binding }); // 25 selections, cap exceeded
// after
const MAX = 10;
const trimmed = selections.slice(0, MAX); // enforce cap client-side before staging
await updateRunSelections(db, runId, trimmed);
await stageNativeRunnerWakeAttachments({ db, binding });
Defensive patterns

Strategy: validation

Validate before calling

const MAX = 10; // keep in sync with MAX_NATIVE_STAGED_ATTACHMENTS
const selections = wakeAttachmentSelections(run.contextSnapshot);
if (selections.length > MAX) throw new Error(`too many wake attachments: ${selections.length} > ${MAX}`);

Type guard

null

Try / catch

try {
  await stageNativeRunnerWakeAttachments({ db, binding });
} catch (err) {
  if (err instanceof Error && err.message === "paperclip_runner_attachment_staging_count_denied") {
    // trim selections to the cap and resubmit the wake
  } else throw err;
}

Prevention

When it happens

Trigger: A wake request with more than the maximum number of attachment selections stored in heartbeatRuns.contextSnapshot, then stageNativeRunnerWakeAttachments is called for that run while it is queued/running and authorized.

Common situations: A user attaching a very large batch of files to wake comments; a client bug accumulating duplicate selections in the snapshot across retries; an older runner snapshot format that did not enforce the client-side limit now being staged by newer server code.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/e36c491540d8b1ee. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/native-runtime/native-runner-file-handoff.ts:875

    )
  ) {
    throw new Error("paperclip_runner_attachment_staging_not_authorized");
  }
  const reviewContext = readNativeReviewAssignmentContext(run.contextSnapshot);
  const nativeReview = reviewContext
    ? await getNativeReviewAssignment(input.db, {
        companyId: input.binding.companyId,
        issueId: input.binding.issueId,
        agentId: input.binding.agentId,
        contextSnapshot: reviewContext,
      })
    : null;
  if (run.assigneeAgentId !== input.binding.agentId && !nativeReview) {
    throw new Error("paperclip_runner_attachment_staging_not_authorized");
  }
  const selections = wakeAttachmentSelections(run.contextSnapshot);
  if (selections.length > MAX_NATIVE_STAGED_ATTACHMENTS) {
    throw new Error("paperclip_runner_attachment_staging_count_denied");
  }
  const workspaceRoot =
    input.binding.executionTargetKind === "local"
      ? await realpath(input.binding.workspaceRoot)
      : null;
  let releaseActiveStage = () => undefined;
  let stagingDestinations: string[] = [];
  if (workspaceRoot) {
    const processDirectoryName = await currentStagingProcessDirectoryName();
    await withStagingRegistryLock(workspaceRoot, async () => {
      const stagingDirectory = await ensurePrivateStagingDirectory(
        workspaceRoot,
        processDirectoryName,
      );
      const activePaths =
        activeStagingPathsByWorkspace.get(workspaceRoot) ?? new Set<string>();
      const reusablePaths = await scrubNativeRunnerStagingResidue(
        workspaceRoot,

View on GitHub (pinned to 3f1d897a7c)