# Contributor handoff — immich-app/immich

> Canonical page: https://codevetter.com/unpack/immich/agent-handoff

# Contributor handoff — immich-app/immich

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

This is a large, convention-heavy monorepo — change one layer and the generated artifacts (OpenAPI spec, SDK, migrations, mobile client) must be regenerated and kept in sync or CI fails. Server code follows strict layering (controller → service → repository → Kysely) with decorator-driven registration that will fail loudly at startup if misconfigured. Tests are fast to run per-package but e2e/medium tiers need Docker. Read the bootstrap and decorator machinery before editing anything structural.
- Read first (server): server/src/main.ts (supervisor), server/src/app.module.ts (module wiring), server/src/enum.ts (all JobName/QueueName/ImmichWorker/Permission values), server/src/decorators.ts (@OnJob/@OnEvent/@Endpoint/@Chunked), server/src/services/base.service.ts (repository injection convention). — [server/src/main.ts](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/main.ts) [server/src/app.module.ts](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/app.module.ts) [server/src/enum.ts](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/enum.ts) [server/src/decorators.ts](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/decorators.ts) [server/src/services/base.service.ts](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/services/base.service.ts)
- Convention: controllers stay thin — they take @Auth() AuthDto + DTOs and delegate to a service; every handler must declare @Authenticated() (defaults to authenticated; use {public:true} or {sharedLink:true} variants) or the guard throws. — [server/src/controllers/asset-media.controller.ts#L43-90](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/controllers/asset-media.controller.ts#L43-L90) [server/src/middleware/auth.guard.ts#L99-123](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/middleware/auth.guard.ts#L99-L123)
- Convention: authorization inside services goes through this.requireAccess({auth, permission, ids}) / checkAccess backed by access.repository.ts — don't hand-roll ownership checks. — [server/src/services/base.service.ts#L284-290](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/services/base.service.ts#L284-L290) [server/src/utils/access.ts](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/utils/access.ts) [server/src/services/asset-media.service.ts#L133-138](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/services/asset-media.service.ts#L133-L138)
- Adding a job is a startup-validated contract: new JobName without an @OnJob handler (or a duplicate handler) aborts boot with ImmichStartupError — always add enum + decorated method + ensure the service is in services/index.ts. — [server/src/repositories/job.repository.ts#L56-87](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/repositories/job.repository.ts#L56-L87) [server/src/services/index.ts](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/services/index.ts)
- API changes must be followed by regeneration: sync-open-api.ts writes the spec, 'mise run open-api-typescript' rebuilds packages/sdk, and the check-openapi CI workflow fails if the committed spec drifts. — [server/src/bin/sync-open-api.ts](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/bin/sync-open-api.ts) [mise.toml](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/mise.toml) [.github/workflows/check-openapi.yml](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/.github/workflows/check-openapi.yml)
- Schema changes: edit table classes under server/src/schema/tables, then 'pnpm migrations:generate' (sql-tools against DB_URL); ordering is enforced by schema/migrations/ORDER and the migration-order.yml workflow — see docs/docs/developer/database-migrations.md. — [server/package.json#L30-38](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/package.json#L30-L38) [server/src/schema/migrations](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/schema/migrations) [.github/workflows/migration-order.yml](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/.github/workflows/migration-order.yml) [docs/docs/developer/database-migrations.md](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/docs/docs/developer/database-migrations.md)
- Test commands: server unit 'pnpm test' (vitest, no DB needed), 'pnpm test:medium' (needs Docker for testcontainers), e2e 'pnpm test'/'test:web' in e2e/ (needs docker compose up), web 'pnpm test'. Lint gates are strict: eslint --max-warnings 0 plus prettier checks. — [server/package.json#L14-29](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/package.json#L14-L29) [e2e/package.json](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/e2e/package.json) [web/package.json](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/web/package.json)
- Known trap: services get repositories positionally through BaseService — adding a repository means updating repositories/index.ts AND the BASE_SERVICE_DEPENDENCIES tuple AND the constructor AND BaseService.create in the same order, or DI silently misaligns. — [server/src/services/base.service.ts#L68-262](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/services/base.service.ts#L68-L262)
- Known trap: file paths on disk must go through StorageCore/StorageRepository helpers (StorageFolder layout, media location from IMMICH_MEDIA_LOCATION) — never construct storage paths ad hoc, and DB-stored paths are updated via MoveRepository. — [server/src/cores/storage.core.ts](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/cores/storage.core.ts) [server/src/repositories/move.repository.ts](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/repositories/move.repository.ts) [server/src/services/storage.service.ts](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/services/storage.service.ts)
- Known trap: long-running requests are normal (24h requestTimeout, streaming uploads/downloads via sendFile/ImmichFileResponse) — avoid buffering whole files in memory and prefer the ImmichFileResponse pattern for binary responses. — [server/src/app.common.ts#L92](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/app.common.ts#L92) [server/src/utils/file.ts](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/utils/file.ts) [server/src/services/asset-media.service.ts#L229-249](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/services/asset-media.service.ts#L229-L249)
- Environment config is centralized: add new env vars to EnvSchema in dtos/env.dto.ts and surface them via ConfigRepository.getEnv(); never read process.env inside services. — [server/src/dtos/env.dto.ts](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/dtos/env.dto.ts) [server/src/repositories/config.repository.ts#L28-120](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/server/src/repositories/config.repository.ts#L28-L120)
- Toolchain is pinned by mise (node 24.21.0, pnpm 11.24.0, java 21 for openapi-generator, extism/binaryen for plugins, jellyfin-ffmpeg for transcoding tests) — use 'mise run //server:ci-unit' style tasks to match CI rather than raw pnpm where possible. — [mise.toml](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/mise.toml) [.github/workflows/test.yml#L123-130](https://github.com/immich-app/immich/blob/d6d094738366aaf4d2fcefdfe2702f50c72ba5b0/.github/workflows/test.yml#L123-L130)

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)
