HeyPuter/puter · error · HttpError

cannot_read_a_directory

cannot_read_a_directory

Error message

Cannot read a directory

What it means

Thrown by LegacyFSController.read after the target entry is resolved and read access is confirmed, when entry.isDir is true. The read endpoint streams file content; directories have no byte content, so the request is rejected with legacyCode 'cannot_read_a_directory'. Use readdir to list directory contents instead.

Source

Thrown at src/backend/controllers/fs/LegacyFSController.ts:1156

        // Legacy v1 /read aliased `file` onto either path or uid depending on
        // whether the value starts with `/`. resolveV1Selector does the same
        // dispatch when handed a raw string.
        const selector =
            typeof query.file === 'string' && query.file.length > 0
                ? query.file
                : query;
        const entry = await resolveV1Selector(this.stores.fsEntry, selector);
        await assertAccess(
            this.services.acl,
            this.services.fs,
            actor,
            entry.path,
            'read',
        );

        if (entry.isDir) {
            throw new HttpError(400, 'Cannot read a directory', {
                legacyCode: 'cannot_read_a_directory',
            });
        }

        const range =
            typeof req.headers.range === 'string'
                ? req.headers.range
                : undefined;
        const download = await this.services.fs.readContent(entry, {
            range,
        });

        // Force `application/octet-stream` on this endpoint for wire parity
        // with v1. puter-js's `parseResponse` branches on Content-Type —
        // `application/octet-stream` returns the raw Blob while other
        // types wrap in `{success, result: Blob}`. Clients (including the
        // GUI) expect the raw-Blob shape. Use `/fs/read` for type-aware
        // streaming.

View on GitHub (pinned to 908ec23eda)

Solutions

  1. Check entry.isDir before reading; if true, call readdir instead.
  2. In the UI, route folder selections to the listing flow and file selections to the read flow.
  3. Resolve/stat the entry first to branch on its type.

Example fix

// before
const blob = await puter.fs.read(entry);

// after
if (entry.is_dir) {
  await puter.fs.readdir(entry);
} else {
  const blob = await puter.fs.read(entry);
}
Defensive patterns

Strategy: type-guard

Validate before calling

const entry = await stat(selector);
if (entry.is_dir) throw new Error('Cannot read a directory; use readdir');

Type guard

function isFileEntry(e) { return !!e && !e.is_dir; }

Try / catch

null

Prevention

When it happens

Trigger: Calling /read (or puter.fs.read) with a path or uid that points to a directory; passing a folder uid where a file is expected.

Common situations: A generic 'open' handler that calls read regardless of entry type; a double-click on a folder inadvertently triggering a read; a stored uid that changed from file to folder.

Related errors


AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12). Data as JSON: /api/errors/537b490020ca25b5. Report an issue: GitHub.