withastro/astro · error · AstroError

UnknownFilesystemError

UnknownFilesystemError

Error message

An unknown error occurred while reading or writing files to disk.

What it means

The mutable data store persists generated asset-import modules (e.g. .astro/assets-imports.mjs) using an atomic temp-file-plus-rename write. When the store has zero asset imports and writing the empty `export default new Map();` stub fails, the raw filesystem error is wrapped in AstroError code UnknownFilesystemError with the original error attached as `cause`.

Solutions

  1. Inspect `error.cause.code` (EACCES, EPERM, ENOSPC, EBUSY) — the real reason is never in the AstroError message itself.
  2. Fix ownership and clear the generated dir: `sudo chown -R $(whoami) .astro` then `rm -rf .astro` and rebuild.
  3. In containers/CI, ensure the user can write the project directory (volume permissions, `user` in docker-compose).
  4. Free disk space or exclude the project folder from antivirus/sync tools if rename locks persist.

Example fix

# before: .astro left root-owned or locked, build fails with UnknownFilesystemError
sudo chown -R "$(whoami)" . .astro
rm -rf .astro
pnpm astro build
Defensive patterns

Strategy: try-catch

Validate before calling

import { access, constants } from 'node:fs/promises';
import { mkdtemp } from 'node:fs/promises';

async function canWriteProjectDir(root: string): Promise<boolean> {
  const probe = await mkdtemp(join(root, '.write-probe-'));
  await rm(probe, { recursive: true });
  return true;
}

Type guard

import { isAstroError } from 'astro/errors';

const isFilesystemError = (err: unknown): err is { code: string } => {
  const cause = (err as any)?.cause ?? err;
  return typeof cause?.code === 'string';
};

Try / catch

try {
  await runAstroBuild(); // programmatic build/sync that writes .astro
} catch (err) {
  if (isAstroError(err) && (err as any).code === 'UnknownFilesystemError') {
    const cause = (err as any).cause;
    if (['EACCES', 'EPERM'].includes(cause?.code)) {
      // fix ownership of .astro / project dir, then retry once
    } else if (cause?.code === 'ENOSPC') {
      // free disk space, then retry
    } else if (cause?.code === 'EBUSY') {
      // another process holds the file: stop duplicate dev servers / AV scan, retry after backoff
    } else {
      throw err;
    }
  } else throw err;
}

Prevention

When it happens

Trigger: Any OS-level failure writing into the .astro directory: EACCES/EPERM (wrong ownership or read-only dir), ENOSPC (full disk), EBUSY/EPERM on rename (file locked by antivirus or a sync client), or a read-only filesystem in a container.

Common situations: Running `astro dev`/`astro build` once under sudo so .astro is root-owned; Docker/CI containers running as a non-root user without write access to the project dir; OneDrive/Dropbox locking generated files; full disks during large builds.

Understand the failure class

Background: "Permission denied" / "Failed to write" file errors: why a library can't write its files to disk (EACCES, EPERM, ENOSPC) and how to fix them — this error's family across 43 libraries.

Related errors


AI-assisted analysis of withastro/astro@e294953aa8 (2026-08-18). Data as JSON: /api/errors/9067ea2d9c9917f5. Report an issue: GitHub.

Appendix: source

Thrown at packages/astro/src/content/mutable-data-store.ts:188

				if (typedEntry.deferredRender && typedEntry.filePath) {
					const id = contentModuleToId(typedEntry.filePath);
					if (id) {
						this.#moduleImports.set(typedEntry.filePath, id);
					}
				}
			}
		}
	}

	async writeAssetImports(filePath: PathLike) {
		this.#assetsFile = filePath;
		this.#rebuildAssetImports();

		if (this.#assetImports.size === 0) {
			try {
				await this.#writeFileAtomic(filePath, 'export default new Map();');
			} catch (err) {
				throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause: err });
			}
		}

		if (!this.#assetsDirty && existsSync(filePath)) {
			return;
		}
		// Import the assets, with a symbol name that is unique to the import id. The import
		// for each asset is an object with path, format and dimensions.
		// We then export them all, mapped by the import id, so we can find them again in the build.
		const imports: Array<string> = [];
		const exports: Array<string> = [];
		// Sort asset imports to ensure deterministic output across builds
		const sortedAssetImports = [...this.#assetImports].sort();
		sortedAssetImports.forEach((id, index) => {
			const symbol = `__ASTRO_IMAGE_IMPORT_${index}`;
			imports.push(`import ${symbol} from ${JSON.stringify(id)};`);
			exports.push(`[${JSON.stringify(id)}, ${symbol}]`);
		});

View on GitHub (pinned to e294953aa8)