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
- Write to a different mount path that resolves to a writable filesystem.
- If the mount should be writable, remove readOnly from its constructor config.
- Check fs.readOnly on the resolved mount in app code before attempting writes.
- 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
- Keep read-only mounts for assets only; route generated content to writable mounts.
- Check fs.readOnly before performing any write-style operation.
- Document mount writability in workspace configuration.
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.