HeyPuter/puter · error · HttpError
not_found
not_found
Error message
Entry not found
What it means
Thrown by FSController.#resolveEntryForRequest when `resolveNode(..., { required: true })` returns null/undefined for a `parent` or `target` reference. The controller builds a ref from `path`, `uid`/`uuid`, or `id`, asks the fsEntry store to resolve it, and 404s if nothing matched. This guards every operation that derefs a node ref (mkshortcut parent/target, copy source/dest, etc.).
Source
Thrown at src/backend/controllers/fs/FSController.ts:1643
rawPath !== undefined
? mod.expandTildePath(rawPath, username)
: undefined,
uid:
typeof source.uid === 'string'
? source.uid
: typeof source.uuid === 'string'
? source.uuid
: undefined,
id:
typeof source.id === 'number' || typeof source.id === 'string'
? source.id
: undefined,
};
const entry = await mod.resolveNode(this.stores.fsEntry, ref, {
required: true,
});
if (!entry) {
throw new HttpError(404, 'Entry not found', {
legacyCode: 'not_found',
});
}
return entry;
}
/**
* Authorize creation of a new entry at `targetPath`. The standard rule is
* write on the parent, but we also accept write on the target itself — this
* lets an app create its own `/<user>/AppData/<app_uid>` folder (parent
* `AppData` is off-limits, but the target is the app's own subtree per
* ACLService's short-circuit) and lets recipients of a direct share on a
* not-yet-existent path materialize it.
*/
async #assertCanCreate(actor: Actor, targetPath: string) {
const parent = pathPosix.dirname(targetPath);
const parentForCheck = parent === '/' ? targetPath : parent;
const fsService = this.services.fs;View on GitHub (pinned to 908ec23eda)
Solutions
- Re-resolve the ref against the source of truth (stat/readdir) and resend with the corrected path/uid/id.
- Prefer uid/uuid over path if the entry may have been renamed between calls.
- If the entry should be shared with the actor, confirm the share record exists and grants at least `see`.
- Handle 404 gracefully in the client and refresh the file listing rather than retrying blindly.
Example fix
// before
{ target: { uid: staleUid } }
// after
const fresh = await puter.fs.stat(targetPath);
{ target: { uid: fresh.uid } } Defensive patterns
Strategy: validation
Validate before calling
async function resolveEntry(ref) {
const entry = await puter.fs.stat(ref.path ?? ref.uid ?? ref.id);
if (!entry) throw new Error(`Entry not found: ${JSON.stringify(ref)}`);
return entry;
} Type guard
/** @param {unknown} r */
function isNodeRef(r) {
return !!r && typeof r === 'object' && ('path' in r || 'uid' in r || 'uuid' in r || 'id' in r);
} Try / catch
try { await op(); }
catch (e) {
if (e?.status === 404) { /* refresh listing, mark node stale */ }
else throw e;
} Prevention
- Prefer uid/uuid over path when the entry may be renamed.
- Stat the entry before mutating if there's any chance it was deleted.
- Treat 404 as authoritative and refresh rather than blind-retry.
When it happens
Trigger: POST /mkshortcut with a `parent`/`target` whose path doesn't exist, was deleted, belongs to another user without share, or whose uid/uuid/id is wrong. Passing a uid that was rotated or an id from a different shard. A race where the entry was deleted between listing and the mutate call.
Common situations: Stale cached uid/uuid on the client after a move/rename; using a path under another user's home without a share grant; copy-pasting a fragment of a uid; referring to an entry by `id` when the column type changed.
Related errors
AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12).
Data as JSON: /api/errors/ad8d4f2e204c1c0f.
Report an issue: GitHub.