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
- Reduce the number of selected attachments in the wake request to at most MAX_NATIVE_STAGED_ATTACHMENTS and re-issue the wake
- Inspect run.contextSnapshot's wake attachment selections and prune duplicates before re-triggering the run
- 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
- Enforce the attachment cap in the wake UI/CLI before selections reach the run snapshot
- Deduplicate selections when accumulating across wake retries
- Split very large attachment batches across multiple runs
- Keep client-side cap constant synchronized with MAX_NATIVE_STAGED_ATTACHMENTS
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
- External chat publications support at most
- Attachment exceeds the configured size limit
- Completion cites no registered attachment on this task. Use…
- External chat attachment ids must be UUIDs
- External chat cards support at most
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)