# Behavior traces — immich-app/immich

> Canonical page: https://codevetter.com/unpack/immich/behavior-traces

# Behavior traces — immich-app/immich

Scanned commit d6d094738366aaf4d2fcefdfe2702f50c72ba5b0 (2026-09-22). Generated by codevetter codevetter 1.12.0 on 2026-09-22.

Key flows were walked end-to-end. Startup goes through a supervisor that gates on a Postgres advisory lock and maintenance mode, then each worker boots its Nest module which emits AppBootstrap to ordered handlers (database migrations first, then storage, then job discovery). The upload flow traverses guard → multer interceptors → controller → service → DB + queue. Background work is decoupled: jobs land in Redis and are executed by the microservices worker via a JobRun event indirection.
- Startup trace: main() reads argv → Workers.bootstrap() → isMaintenanceMode() (SystemMetadataKey.MaintenanceMode, tolerates missing table via PostgresError 42P01) → waitForFreeLock() (pg_try_advisory_lock loop, 1s poll) → startWorker() per configured ImmichWorker. — [server/src/main.ts#L30-64](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/main.ts#L30-L64) [server/src/main.ts#L66-126](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/main.ts#L66-L126)
- API worker boot trace: workers/api.ts bootstrap() → configureTelemetry() → NestFactory.create(ApiModule, {routeConflictPolicy duplicate:error, routeResolutionStrategy specificity}) → AppRepository.setCloseFn → configureExpress(app, {ssr: ApiService}) which installs middleware, sirv, swagger, and listens on port 2283. — [server/src/workers/api.ts#L9-24](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/workers/api.ts#L9-L24) [server/src/app.common.ts#L24-95](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/app.common.ts#L24-L95)
- Nest module init trace: BaseModule.onModuleInit → telemetryRepository.setup → queueService.setServices(services) → websocketRepository.setAuthFn(authenticate) → eventRepository.setup({services}) → emit('AppBootstrap'); onModuleDestroy emits AppShutdown and tears down telemetry. — [server/src/app.module.ts#L82-102](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/app.module.ts#L82-L102)
- Bootstrap ordering is priority-driven: DatabaseService.onBootstrap (priority -200) checks Postgres version range, manages the vector extension (create/update/drop, reindexVectorsIfNeeded), and runs migrations under DatabaseLock.Migrations unless DB_SKIP_MIGRATIONS; StorageService (-195) and JobService (-190) follow, SystemConfig at +100, PluginSync/PluginLoad at +190/+200. — [server/src/services/database.service.ts#L56-120](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/services/database.service.ts#L56-L120) [server/src/enum.ts#L801-813](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/enum.ts#L801-L813)
- Job-handler discovery trace: QueueService.onBootstrap (AppBootstrap listener) → jobRepository.setup(services) reflects over every service method, registers those with @OnJob metadata into handlers{}, and throws ImmichStartupError if any JobName value lacks a handler or two handlers claim one job; microservices then startWorkers() and the api worker starts watchWorkers() (30s liveness poll). — [server/src/repositories/job.repository.ts#L40-104](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/repositories/job.repository.ts#L40-L104) [server/src/services/queue.service.ts#L78-86](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/services/queue.service.ts#L78-L86)
- Upload trace: POST /api/assets → AuthGuard.canActivate (Permission.AssetUpload, sharedLink ok) → AssetUploadInterceptor/FileUploadInterceptor multer writes file → AssetMediaController.uploadAsset → service.uploadAsset → requireAccess + requireQuota → onBeforeLink for livePhotoVideoId → assetRepository.create → upsertMetadata/upsertFile/upsertExif → jobRepository.queue(AssetExtractMetadata) → eventRepository.emit(AssetCreate) → 201 or 200-on-duplicate. — [server/src/controllers/asset-media.controller.ts#L51-90](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/controllers/asset-media.controller.ts#L51-L90) [server/src/services/asset-media.service.ts#L125-227](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/services/asset-media.service.ts#L125-L227)
- Metadata extraction trace: microservices bullmq Worker dequeues → eventRepository.emit('JobRun', queueName, job) → JobRepository.run dispatches → MetadataService.handleMetadataExtraction → getExifTags (exiftool) + stat → getDates/getImageDimensions → reverseGeocode if GPS → upsert → downstream AssetMetadataExtracted event queues thumbnails, smart search, and face detection. — [server/src/services/metadata.service.ts#L215-260](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/services/metadata.service.ts#L215-L260) [server/src/repositories/job.repository.ts#L137-160](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/repositories/job.repository.ts#L137-L160)
- Login trace: POST /auth/login → AuthService.login → getConfig(passwordLogin.enabled gate) → userRepository.getByEmail(withPassword) → cryptoRepository.compareBcrypt against user password or LOGIN_DUMMY_HASH for constant-time behavior → createLoginResponse(user, details) issues session + cookies. — [server/src/services/auth.service.ts#L60-77](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/services/auth.service.ts#L60-L77) [server/src/constants.ts](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/constants.ts)
- Nightly tasks trace: on ConfigInit the non-microservices worker takes DatabaseLock.NightlyJobs, converts config.nightlyTasks.startTime to a cron expression, and registers CronJob.NightlyJobs whose onTick calls handleNightlyJobs(); ConfigUpdate re-schedules it. — [server/src/services/queue.service.ts#L34-76](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/services/queue.service.ts#L34-L76)
- Worker crash/restart trace: any worker 'exit' triggers onExit; exitCode ExitCode.AppRestart sets restarting=true and reboots all workers once drained; a non-zero exit otherwise kills the api worker (SIGTERM) if present and exits the supervisor process with the same code. — [server/src/main.ts#L128-162](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/main.ts#L128-L162)
- Websocket connect trace: socket.io handshake → WebSocketAdapter (redis pub/sub adapter) → authFn registered in BaseModule → AuthService.authenticate with uri '/api/socket.io' → WebsocketConnect event emitted with userId. — [server/src/app.module.ts#L87-93](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/app.module.ts#L87-L93) [server/src/middleware/websocket.adapter.ts](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/middleware/websocket.adapter.ts) [server/src/repositories/event.repository.ts#L106-107](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/repositories/event.repository.ts#L106-L107)
- ML predict trace: POST /predict → get_entries parses multipart 'entries' JSON into InferenceEntry list splitting with/without model deps → run_inference resolves deps (detection output feeds recognition) → model_cache.get + load() (ONNX fallback if other format unsupported) → model.predict in thread pool → ORJSONResponse. — [machine-learning/immich_ml/main.py#L132-248](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/machine-learning/immich_ml/main.py#L132-L248)

Every claim links to its cited files at the pinned commit. Citations identify source evidence; they do not establish runtime correctness. Independent CodeVetter scan — not reviewed by upstream maintainers.

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
