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
- Verify the UID/path exists (stat) before using it as a selector.
- Refresh cached UIDs when a 404 is returned, since the entry may have been deleted.
- 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
- Stat before operating on a cached ref.
- Refresh UIDs on 404; the entry may be gone.
- Verify path casing and leading slash.
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
- Access denied
- not_found
- ${key} must be an integer between ${min} and ${max}
- Missing `app_uuid`
- bad_request
AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12).
Data as JSON: /api/errors/319198313a051fad.
Report an issue: GitHub.