mastra-ai/mastra · error · FileNotFoundError

FileNotFoundError: ${path}

Error message

FileNotFoundError: ${path}

What it means

readFile() runs a shell probe in the sandbox that classifies the target before reading: directories exit with EXIT_IS_DIRECTORY and non-existent paths exit with EXIT_NOT_FOUND, so the sandbox throws FileNotFoundError(path) instead of attempting a base64 read. This gives callers a typed, distinguishable signal that the requested path does not exist inside the sandbox workspace (after symlink-containment checks passed).

Source

Thrown at mastracode/sdk/src/agents/sandbox-filesystem.ts:256

    const result = await this.exec(script);
    if (result.exitCode !== 0) {
      throw new Error(`${context} failed (exit ${result.exitCode}): ${result.stderr.trim() || result.stdout.trim()}`);
    }
    return result;
  }

  // ── File operations ────────────────────────────────────────────────────

  async readFile(path: string, options?: ReadOptions): Promise<string | Buffer> {
    const abs = await this.resolveAsync(path);
    await this.assertContainedRealpath(abs, path);
    // Guard clauses first: redirecting from a directory "succeeds" with empty
    // output on some shells, so classify before reading.
    const result = await this.exec(
      `if [ -d ${shellQuote(abs)} ]; then exit ${EXIT_IS_DIRECTORY}; elif [ ! -e ${shellQuote(abs)} ]; then exit ${EXIT_NOT_FOUND}; fi; base64 < ${shellQuote(abs)}`,
    );
    if (result.exitCode === EXIT_IS_DIRECTORY) throw new IsDirectoryError(path);
    if (result.exitCode === EXIT_NOT_FOUND) throw new FileNotFoundError(path);
    if (result.exitCode !== 0) {
      throw new Error(`readFile ${path} failed (exit ${result.exitCode}): ${result.stderr.trim()}`);
    }
    const buffer = Buffer.from(result.stdout.replace(/\s/g, ''), 'base64');
    if (options?.encoding) {
      return buffer.toString(options.encoding);
    }
    return buffer;
  }

  async writeFile(path: string, content: FileContent, options?: WriteOptions): Promise<void> {
    const abs = await this.resolveAsync(path);
    await this.assertContainedDest(abs, path);
    const b64 = toBuffer(content).toString('base64');
    const dir = posixPath.dirname(abs);
    const mkdir = options?.recursive === false ? '' : `mkdir -p ${shellQuote(dir)} && `;
    if (options?.overwrite === false) {
      // `set -C` (noclobber) makes the redirect itself the exclusivity check —

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Check the file exists (or create it) before reading: verify the producing step (writeFile/copyFile) ran first.
  2. Print/verify the exact path being passed and how it resolves relative to the sandbox basePath — remove leading host-absolute segments if the file lives inside the workdir.
  3. Use the force/idempotent pattern on the write side (or copyFile) to ensure the file is present before read.
  4. Wrap in try/catch on FileNotFoundError and treat as an expected 'missing' outcome rather than a crash.

Example fix

// before\nconst content = await sandbox.fs.readFile('output/result.json');\n// after\nlet content: string;\ntry {\n  content = await sandbox.fs.readFile('output/result.json', { encoding: 'utf8' });\n} catch (e) {\n  if (e instanceof FileNotFoundError) content = '';\n  else throw e;\n}
Defensive patterns

Strategy: try-catch

Validate before calling

let exists = false;\ntry {\n  await fs.readFile(path);\n  exists = true;\n} catch { /* probe read */ }

Type guard

function isFileNotFoundError(e: unknown): e is FileNotFoundError {\n  return e instanceof FileNotFoundError;\n}

Try / catch

try {\n  const content = await fs.readFile(path);\n} catch (e) {\n  if (e instanceof FileNotFoundError) {\n    // handle missing file: default value or early return\n  } else {\n    throw e;\n  }\n}

Prevention

When it happens

Trigger: Calling sandbox.files.readFile(path) when the file does not exist at the resolved workspace-relative path: a typo'd path, reading a file that was deleted or never written, reading a path that only exists on the host but not in the sandbox workdir, or a case-sensitivity mismatch (Linux sandbox vs macOS host).

Common situations: Agents reading generated output before the producing step ran; stale cache of previously-existing files; tests asserting on fixture files not copied into the sandbox workdir; referencing absolute host paths that get resolved inside the sandbox where they don't exist.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/df01cd4400101584. Report an issue: GitHub.