immich-app/immich · error · BadRequestException

User not found

Error message

User not found

What it means

A BadRequestException (HTTP 400) thrown by UserService.getMe when the authenticated user's record cannot be loaded from the database. The auth guard already validated the session/JWT, so reaching this branch means the user row is gone between authentication and the getMe query. It is a data-integrity signal rather than a normal client error.

Source

Thrown at server/src/services/user.service.ts:46

export class UserService extends BaseService {
  async search(auth: AuthDto): Promise<UserResponseDto[]> {
    const config = await this.getConfig({ withCache: false });

    let users;
    if (auth.user.isAdmin || config.server.publicUsers) {
      users = await this.userRepository.getList({ withDeleted: false });
    } else {
      const authUser = await this.userRepository.get(auth.user.id, {});
      users = authUser ? [authUser] : [];
    }

    return users.map((user) => mapUser(user));
  }

  async getMe(auth: AuthDto): Promise<UserAdminResponseDto> {
    const user = await this.userRepository.get(auth.user.id, {});
    if (!user) {
      throw new BadRequestException('User not found');
    }

    return mapUserAdmin(user);
  }

  getCalendarHeatmap(auth: AuthDto, dto: CalendarHeatmapDto): Promise<CalendarHeatmapResponseDto> {
    return getCalendarHeatmap(auth.user.id, dto, { asset: this.assetRepository });
  }

  async updateMe({ user }: AuthDto, dto: UserUpdateMeDto): Promise<UserAdminResponseDto> {
    if (dto.email) {
      const duplicate = await this.userRepository.getByEmail(dto.email);
      if (duplicate && duplicate.id !== user.id) {
        this.logger.warn('Email already in use by another account');
        throw new BadRequestException('Email is not available');
      }
    }

View on GitHub (pinned to 199723261c)

Solutions

  1. If this is an end-user client, treat it as a forced logout: clear the local session/token and redirect to login.
  2. If you are an admin automating user lifecycle, ensure no getMe calls are in flight when you delete a user; tear down sessions first.
  3. Check the user table for the id in the token to confirm whether the row was deleted or the id is malformed.
  4. In tests, re-authenticate or skip getMe after deleting the acting user.

Example fix

// before
const me = await api.getUserMe(); // 400 'User not found'

// after
try {
  const me = await api.getUserMe();
} catch (e) {
  if (e.status === 400 && e.message === 'User not found') {
    await auth.clearSession();
    router.push('/login');
    return;
  }
  throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// No client-side validation can guarantee the row exists; verify right before the call
const stillExists = await adminApi.getUser(authUserId);
if (!stillExists) { await clearSession(); return; }

Type guard

const isUserMissingError = (e: unknown): boolean =>
  typeof e === 'object' && e !== null && (e as any).status === 400 && (e as any).message === 'User not found';

Try / catch

try {
  return await api.getUserMe();
} catch (e) {
  if (isUserMissingError(e)) {
    await auth.clearSession();
    redirectToLogin();
    return null;
  }
  throw e;
}

Prevention

When it happens

Trigger: GET /users/me issued after the user was soft- or hard-deleted (e.g., by an admin) but the client still holds a valid session token. Also reachable via test harnesses that authenticate then delete the user, or a race where a deletion job commits between auth and the repository.get call.

Common situations: Stale tokens in a browser after an admin deletes the account; integration tests that delete users mid-flow; database restores that drop user rows but keep sessions; multi-instance setups where deletion replication lags.

Related errors


AI-assisted analysis of immich-app/immich@199723261c (2026-08-12). Data as JSON: /api/errors/5f4f1e179ae5e464. Report an issue: GitHub.