withastro/astro · warning

Error when reading content directory "${contentDir}"

Error message

Error when reading content directory "${contentDir}"

What it means

During content-layer sync, Astro scans the content directory for symbolic links and resolves each one with fs.realpath so symlinked entries map to their real files. If readdir() or realpath() throws, Astro logs this warning (the underlying error only at debug level) and falls back to an empty symlink map, so sync continues but symlinked content entries are not tracked.

Source

Thrown at packages/astro/src/content/utils.ts:298

	try {
		if (!fs.existsSync(contentDirPath) || !fs.lstatSync(contentDirPath).isDirectory()) {
			return contentPaths;
		}
	} catch {
		// Ignore if there isn't a valid content directory
		return contentPaths;
	}
	try {
		const contentDirEntries = await fs.promises.readdir(contentDir, { withFileTypes: true });
		for (const entry of contentDirEntries) {
			if (entry.isSymbolicLink()) {
				const entryPath = path.join(contentDirPath, entry.name);
				const realPath = await fs.promises.realpath(entryPath);
				contentPaths.set(normalizePath(realPath), entry.name);
			}
		}
	} catch (e) {
		logger.warn('content', `Error when reading content directory "${contentDir}"`);
		logger.debug('content', e);
		// If there's an error, return an empty map
		return new Map<string, string>();
	}

	return contentPaths;
}

export function reverseSymlink({
	entry,
	symlinks,
	contentDir,
}: {
	entry: string | URL;
	contentDir: string | URL;
	symlinks?: Map<string, string>;
}): string {
	const entryPath = normalizePath(typeof entry === 'string' ? entry : fileURLToPath(entry));

View on GitHub (pinned to e294953aa8)

Solutions

  1. Find and repair dangling links: run `find src/content -xtype l` and remove or relink each match
  2. Verify the content directory exists and every symlink target is readable by the dev-server process
  3. Restart `astro dev` or re-run `astro sync` so the content layer re-scans
  4. If the warning persists, re-run with debug logging (logger.debug shows the caught error) to see the exact fs failure

Example fix

# before: dangling symlink under src/content
find src/content -xtype l
# src/content/posts -> ../../shared/posts (target moved)

# after: relink to an existing, readable target
ln -sfn ../../packages/shared/posts src/content/posts
Defensive patterns

Strategy: validation

Validate before calling

// run before `astro dev` / in CI — fails on dangling symlinks under src/content
import { readdirSync, lstatSync, readlinkSync, existsSync } from 'node:fs';
import path from 'node:path';

for (const name of readdirSync('src/content')) {
  const p = path.join('src/content', name);
  if (lstatSync(p).isSymbolicLink()) {
    const target = path.resolve(path.dirname(p), readlinkSync(p));
    if (!existsSync(target)) throw new Error(`Dangling content symlink: ${p} -> ${target}`);
  }
}

Prevention

When it happens

Trigger: fs.promises.readdir(contentDir) or fs.promises.realpath(entryPath) throws: the content directory was deleted or renamed while `astro dev` / `astro sync` was running, a dangling (broken) symlink exists under src/content (realpath returns ENOENT), or the process lacks read permission on the directory or a link target.

Common situations: Monorepos and npm/pnpm workspaces where src/content contains symlinks into a shared package; deleting or moving content files while the dev server is mid-sync; CI containers or read-only mounts where realpath fails on permission-restricted targets.

Related errors


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