mastra-ai/mastra · error · PermissionError

EACCES

EACCES

Error message

Permission denied: ${operation} (filesystem is read-only) on ${path}

What it means

A PermissionError (code EACCES) thrown by assertWritable when a write-style operation targets a mount whose filesystem is read-only. CompositeFilesystem derives its own readOnly flag from whether all mounts are read-only, and write methods (writeFile, appendFile, deleteFile, copyFile, moveFile, mkdir) check the resolved mount before delegating. This protects backends mounted intentionally as immutable (e.g., read-only templates or shared assets).

Source

Thrown at packages/core/src/workspace/filesystem/composite-filesystem.ts:264

    return entriesMap.size > 0 ? Array.from(entriesMap.values()) : null;
  }

  private isVirtualPath(path: string): boolean {
    const normalized = this.normalizePath(path);
    if (normalized === '/' && !this._mounts.has('/')) return true;
    for (const mountPath of this._mounts.keys()) {
      if (mountPath.startsWith(normalized + '/')) return true;
    }
    return false;
  }

  /**
   * Assert that a filesystem is writable (not read-only).
   * @throws {PermissionError} if the filesystem is read-only
   */
  private assertWritable(fs: WorkspaceFilesystem, path: string, operation: string): void {
    if (fs.readOnly) {
      throw new PermissionError(path, `${operation} (filesystem is read-only)`);
    }
  }

  // ===========================================================================
  // WorkspaceFilesystem Implementation
  // ===========================================================================

  async init(): Promise<void> {
    this.status = 'initializing';
    for (const [mountPath, fs] of this._mounts.entries()) {
      try {
        await callLifecycle(fs, 'init');
      } catch (e) {
        // Individual mount failed - it will have status='error'
        // Log but continue with other mounts
        const message = e instanceof Error ? e.message : String(e);
        console.warn(`[CompositeFilesystem] Mount "${mountPath}" failed to initialize: ${message}`);
      }

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Write to a different mount path that resolves to a writable filesystem.
  2. If the mount should be writable, remove readOnly from its constructor config.
  3. Check fs.readOnly on the resolved mount in app code before attempting writes.
  4. Route generated/user content to a dedicated writable mount (e.g., '/data') and keep read-only assets separate.

Example fix

// before
new CompositeFilesystem({ mounts: { '/assets': new LocalFilesystem('/srv/assets', { readOnly: true }), '/': rwFs } });
await fs.writeFile('/assets/out.txt', 'x'); // EACCES
// after
await fs.writeFile('/out.txt', 'x'); // writes via writable '/' mount
Defensive patterns

Strategy: try-catch

Validate before calling

// resolve the same way the composite does, or expose fs.readOnly
const mount = mounts[normalizePrefix(path)];
if (mount?.readOnly) throw new Error(`Refusing write to read-only mount at ${path}`);

Type guard

function isWritable(fs: WorkspaceFilesystem): boolean { return !fs.readOnly; }

Try / catch

try { await composite.writeFile(path, data); } catch (e) { if ((e as any).code === 'EACCES' || /read-only/.test((e as Error).message)) { await composite.writeFile(fallbackWritablePath(path), data); } else throw e; }

Prevention

When it happens

Trigger: Calling writeFile/appendFile/deleteFile/copyFile/moveFile/mkdir with a path that resolves to a mount constructed with readOnly: true (or whose underlying fs reports readOnly).

Common situations: Mounting a directory as read-only for reference but application code assumes it's writable; env/config marking production asset mounts read-only; writing generated output into a templates mount instead of a writable data mount.

Related errors


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