paperclipai/paperclip · error

Bridge envelope exceeded the configured size limit.

Error message

Bridge envelope exceeded the configured size limit.

What it means

The file-system sandbox callback bridge queue client's readTextFile(path, maxBytes) stats the remote envelope file first and throws "Bridge envelope exceeded the configured size limit." when the file on disk is larger than the caller-provided maxBytes budget. This bounds memory allocation so a runaway or oversized request/response JSON file can never be read whole into the host process.

Solutions

  1. Increase the bridge's maxBodyBytes / maxEnvelopeBytes configuration to cover legitimate payloads.
  2. Delete or drain oversized stale envelope files from the requests directory so the poller stops tripping on them.
  3. On the producer side, check body size against encodeSandboxBridgeBody's limit before writing the request file, and split or compress the payload.
  4. Check for writers appending to the same request path without atomic rename; the bridge writes via temp-file + rename to avoid this.
  5. If the file is smaller than intended, re-stat — a concurrent writer may have been mid-write; retry after the writer completes.

Example fix

// before: reading with a tight limit trips on a 2MB envelope
const raw = await client.readTextFile(requestPath, 512 * 1024);
// after: size the budget from the same formula the gateway uses
const maxEnvelopeBytes = 6 * maxBodyBytes + 64 * 1024;
const raw = await client.readTextFile(requestPath, Math.min(fileSize, maxEnvelopeBytes));
Defensive patterns

Strategy: validation

Validate before calling

const maxEnvelopeBytes = 6 * maxBodyBytes + 64 * 1024;
const size = await client.fileSize(requestPath);
if (size > maxEnvelopeBytes) throw new Error(`Envelope ${size} bytes exceeds limit ${maxEnvelopeBytes}`);

Try / catch

try {
  const raw = await client.readTextFile(requestPath, readLimit);
} catch (error) {
  if ((error as Error).message.includes("exceeded the configured size limit")) {
    await finalize({ id, status: 413, body: JSON.stringify({ error: "envelope too large" }) });
    return; // do not retry with the same limit
  }
  throw error;
}

Prevention

When it happens

Trigger: Calling readTextFile with a maxBytes argument while the `.json` envelope file at remotePath has a stat.size greater than maxBytes — e.g. the gateway reads request files with a read limit derived from maxEnvelopeBytes (6 * maxBodyBytes + 64KiB) and a producer wrote an envelope bigger than that budget.

Common situations: A sandbox job POSTs a body larger than the configured maxBodyBytes so the encoded envelope exceeds the envelope limit; maxBodyBytes/maxEnvelopeBytes were lowered in config while old, larger envelopes still sit in the requests directory; a stuck/duplicate writer grew the file beyond the limit before the reader picked it up.

Understand the failure class

Background: "File too large" / "file size exceeds limit" errors: why libraries cap file sizes and how to fix them — this error's family across 46 libraries.

Related errors


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

Appendix: source

Thrown at packages/adapter-utils/src/sandbox-callback-bridge.ts:533

    makeDirs: async (remotePaths) => {
      for (const remotePath of remotePaths) {
        await fs.mkdir(remotePath, { recursive: true });
      }
    },
    listJsonFiles: async (remotePath) => {
      const entries = await fs.readdir(remotePath, { withFileTypes: true }).catch(() => []);
      return entries
        .filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
        .map((entry) => entry.name)
        .sort((left, right) => left.localeCompare(right));
    },
    fileSize: async (remotePath) => (await fs.stat(remotePath)).size,
    readTextFile: async (remotePath, maxBytes) => {
      if (maxBytes === undefined) return fs.readFile(remotePath, "utf8");
      const file = await fs.open(remotePath, "r");
      try {
        const stat = await file.stat();
        if (stat.size > maxBytes) throw new Error("Bridge envelope exceeded the configured size limit.");
        const bytes = Buffer.alloc(Math.min(stat.size, maxBytes) + 1);
        let length = 0;
        while (length < bytes.length) {
          const read = await file.read(bytes, length, bytes.length - length, length);
          if (!read.bytesRead) break;
          length += read.bytesRead;
        }
        if (length > stat.size) throw new Error("Bridge envelope changed while reading.");
        return bytes.subarray(0, length).toString("utf8");
      } finally { await file.close(); }
    },
    writeTextFile: async (remotePath, body) => {
      await fs.mkdir(path.posix.dirname(remotePath), { recursive: true });
      // Write to a temporary path that does NOT end in `.json`, then rename it
      // onto the final `.json` path. A direct `writeFile` truncates the final
      // path first, so a `.json`-only reader (the stdin poller) can see an
      // empty or partial file. The atomic rename never exposes partial content.
      const tempPath = `${remotePath}.paperclip-upload.decoded`;

View on GitHub (pinned to 3f1d897a7c)