HeyPuter/puter · error · HttpError

not_found

not_found

Error message

Entry not found: ${raw}

What it means

In `resolveV1Selector`, a plain-string selector is treated as a path (leading `/` or `~`) or a UID (anything else) and resolved via `resolveNode` with `required: true`. If resolution returns nothing, the helper throws 404 (`not_found`) echoing the raw input. This is the v1 'entry not found' for string refs.

Source

Thrown at src/backend/controllers/fs/legacyFsHelpers.ts:113

// expansion via its own `#normalizePath` helper.
export async function resolveV1Selector(
    fsEntryStore: FSEntryStore,
    raw: unknown,
): Promise<FSEntry> {
    const username = Context.get('actor')?.user?.username;

    // String shorthand — either an absolute path (`/a/b/c`) or a UUID.
    // The legacy API accepts both interchangeably; dispatch on the leading
    // character rather than guessing by regex. Anything that doesn't start
    // with `/` is treated as a uid. Tilde-rooted paths are path-shaped.
    if (typeof raw === 'string') {
        const isPath = raw.startsWith('/') || raw.startsWith('~');
        const ref = isPath
            ? { path: expandTildePath(raw, username) }
            : { uid: raw };
        const entry = await resolveNode(fsEntryStore, ref, { required: true });
        if (!entry)
            throw new HttpError(404, `Entry not found: ${raw}`, {
                legacyCode: 'not_found',
            });
        return entry;
    }

    const record = asRecord(raw);

    // {parent, name}: "child selector" — resolve parent, then child by name.
    if (record.parent !== undefined && typeof record.name === 'string') {
        const parent = await resolveV1Selector(fsEntryStore, record.parent);
        const childPath = joinChildPath(parent.path, record.name);
        const child = await resolveNode(
            fsEntryStore,
            { path: childPath },
            { required: true },
        );
        if (!child)
            throw new HttpError(404, `Entry not found: ${childPath}`, {

View on GitHub (pinned to 908ec23eda)

Solutions

  1. Verify the UID/path exists (stat) before using it as a selector.
  2. Refresh cached UIDs when a 404 is returned, since the entry may have been deleted.
  3. Double-check path spelling, casing, and leading slash.

Example fix

// before
await puter.fs.read({ uid: staleUid }); // entry was deleted

// after
let entry;
try { entry = await puter.fs.stat({ uid }); }
catch { entry = await puter.fs.stat('/path/to/file'); }
await puter.fs.read(entry.uid);
Defensive patterns

Strategy: try-catch

Validate before calling

async function entryExists(stat, ref) { try { await stat(ref); return true; } catch { return false; } }

Type guard

function isStringSelector(v) { return typeof v === 'string' && v.length > 0; }

Try / catch

try { return await resolve(ref); }
catch (e) { if (e.status === 404) { ref = await refreshRef(); return await resolve(ref); } throw e; }

Prevention

When it happens

Trigger: Passing a UID that doesn't exist, a path with a typo, or a path inside a folder that was deleted/moved. The raw string is interpolated into the message for debugging.

Common situations: Client cached a UID after the entry was deleted; wrong path casing or trailing slash; referring to another user's private path; race where the entry is removed between list and operation.

Related errors


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