immich-app/immich · error · BadRequestException
not found
Error message
${entity} not found What it means
findOrFail() is a helper that wraps an async lookup and converts a null/undefined result into a NestJS BadRequestException with '<entity> not found'. It centralizes not-found handling for repository lookups so callers get a clean 400 instead of propagating undefined.
Solutions
- Verify the requested entity ID exists (and belongs to the authenticated user) before the call.
- Refresh client-side state/list to drop stale references to deleted entities.
- If the entity is legitimately optional, use the raw find call instead of findOrFail and handle null yourself.
- Check the message prefix ('<entity> not found') to identify which lookup failed and fix the upstream data.
Defensive patterns
Strategy: try-catch
Validate before calling
const exists = asset && (await repo.get(asset.id)) !== null; if (!exists) return null; // avoid triggering not-found
Type guard
const isNotFound = (e: unknown): e is BadRequestException => e instanceof BadRequestException && / not found$/.test(e.message);
Try / catch
try { const user = await findOrFail(() => repo.get(id), 'User'); } catch (e) { if (isNotFound(e)) return res.status(404).json({ error: 'User not found' }); throw e; } Prevention
- Validate IDs before lookup where possible
- Treat 404 as expected for user-facing lookups, not as a bug
- Refresh client caches to drop references to deleted entities
When it happens
Trigger: Any code path calling findOrFail with a finder function whose query matched no row, e.g. requesting a nonexistent asset/user/album ID via an API endpoint.
Common situations: Client requesting an asset that was deleted server-side or belongs to another user; stale IDs cached in a client; typos in IDs; race where the entity is removed between listing and fetching.
Understand the failure class
Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.
Related errors
AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15).
Data as JSON: /api/errors/4368869ec6fd31d7.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/utils/misc.ts:114
const isMachineLearningEnabled = (machineLearning: SystemConfig['machineLearning']) => machineLearning.enabled;
export const isSmartSearchEnabled = (machineLearning: SystemConfig['machineLearning']) =>
isMachineLearningEnabled(machineLearning) && machineLearning.clip.enabled;
export const isOcrEnabled = (machineLearning: SystemConfig['machineLearning']) =>
isMachineLearningEnabled(machineLearning) && machineLearning.ocr.enabled;
export const isFacialRecognitionEnabled = (machineLearning: SystemConfig['machineLearning']) =>
isMachineLearningEnabled(machineLearning) && machineLearning.facialRecognition.enabled;
export const isDuplicateDetectionEnabled = (machineLearning: SystemConfig['machineLearning']) =>
isSmartSearchEnabled(machineLearning) && machineLearning.duplicateDetection.enabled;
export const isFaceImportEnabled = (metadata: SystemConfig['metadata']) => metadata.faces.import;
export const handlePromiseError = <T>(promise: Promise<T>, logger: LoggingRepository): void => {
promise.catch((error: Error | any) => logger.error(`Promise error: ${error}`, error?.stack));
};
export const findOrFail = async <T>(find: () => Promise<T>, entity: string): Promise<NonNullable<T>> => {
const value = await find();
if (!value) {
throw new BadRequestException(`${entity} not found`);
}
return value;
};
export async function* batched<T>(items: AsyncIterable<T>, size = JOBS_ASSET_PAGINATION_SIZE): AsyncGenerator<T[]> {
let batch: T[] = [];
for await (const item of items) {
batch.push(item);
if (batch.length >= size) {
yield batch;
batch = [];
}
}
if (batch.length > 0) {View on GitHub (pinned to e55ac299a4)