paperclipai/paperclip · error

ENOENT

ENOENT

Error message

No such file: ${remotePath}

What it means

ENOENT-contract emulation in the shell-backed sandbox filesystem client: readFile first runs `wc -c < remotePath` to bound chunked reads; when that shell command fails and the parent directory is confirmed searchable (so it is not a permission/transport problem), the client normalizes the failure to the same 'No such file' error the fs API would throw for an absent file. It is a deliberate sentinel mapping, not an unexpected crash.

Solutions

  1. Treat this error like fs ENOENT: check the remote path exists (correct cwd/absolute path) before reading.
  2. If the file should exist, verify it was created by an earlier step and the path spelling (case, separators) matches.
  3. For deletion races, re-check existence or tolerate absence in the caller before reading.
Defensive patterns

Strategy: try-catch

When it happens

Trigger: Thrown at packages/adapter-utils/src/command-managed-runtime.ts:354 when the library encounters an invalid state.

Common situations: See trigger scenarios.


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

Appendix: source

Thrown at packages/adapter-utils/src/command-managed-runtime.ts:354

    },
    readFile: async (remotePath, options) => {
      // Chunked reads intentionally query the remote size first, even without
      // a progress sink, so each sandbox RPC stays bounded and truncation is
      // detected without materializing the whole file as one stdout string.
      let sizeResult;
      try {
        sizeResult = await runShell(`wc -c < ${shellQuote(remotePath)}`);
      } catch (error) {
        // Shell-backed sandbox reads need the same absent-file contract as fs.
        // Confirm the parent is searchable so permission/transport failures are
        // never silently converted into a missing optional credential file.
        const parent = shellQuote(path.posix.dirname(remotePath));
        const missing = await runShell(
          `if [ -d ${parent} ] && [ -x ${parent} ] && [ ! -e ${shellQuote(remotePath)} ]; ` +
            `then printf 'missing'; fi`,
        ).catch(() => null);
        if (missing?.stdout === "missing") {
          throw Object.assign(new Error(`No such file: ${remotePath}`), {
            code: "ENOENT",
            path: remotePath,
          });
        }
        throw error;
      }
      const totalBytes = Number.parseInt(sizeResult.stdout.trim(), 10);
      if (!Number.isFinite(totalBytes) || totalBytes < 0) {
        throw new Error(`Could not determine remote file size for ${remotePath}`);
      }

      // Read in bounded remote chunks so the runner never has to materialize a
      // single base64 stdout string for the whole archive. The client API still
      // returns the decoded file as a Buffer, but every command result stays
      // small enough for provider-backed sandbox RPCs.
      const decodedChunks: Buffer[] = [];
      let decodedSoFar = 0;
      if (totalBytes === 0) {

View on GitHub (pinned to 3f1d897a7c)