immich-app/immich · warning · BadRequestException

Partner not found

Error message

Partner not found

What it means

Thrown by PartnerService.remove when partnerRepository.get(partnerId) returns null. The caller is attempting to unshare from a user they never shared with. BadRequestException -> HTTP 400.

Source

Thrown at server/src/services/partner.service.ts:33

    if (exists) {
      throw new BadRequestException(`Partner already exists`);
    }

    const user = await this.userRepository.get(sharedWithId, {});
    if (!user) {
      this.logger.debug('Partner creation failed: user not found');
      throw new BadRequestException('Invalid user');
    }

    const partner = await this.partnerRepository.create(partnerId);
    return this.mapPartner(partner, PartnerDirection.SharedBy);
  }

  async remove(auth: AuthDto, sharedWithId: string): Promise<void> {
    const partnerId: PartnerIds = { sharedById: auth.user.id, sharedWithId };
    const partner = await this.partnerRepository.get(partnerId);
    if (!partner) {
      throw new BadRequestException('Partner not found');
    }

    await this.partnerRepository.remove(partnerId);
  }

  async search(auth: AuthDto, { direction }: PartnerSearchDto): Promise<PartnerResponseDto[]> {
    const partners = await this.partnerRepository.getAll(auth.user.id);
    const key = direction === PartnerDirection.SharedBy ? 'sharedById' : 'sharedWithId';
    return partners
      .filter((partner): partner is Partner => !!(partner.sharedBy && partner.sharedWith)) // Filter out soft deleted users
      .filter((partner) => partner[key] === auth.user.id)
      .map((partner) => this.mapPartner(partner, direction));
  }

  async update(auth: AuthDto, sharedById: string, dto: PartnerUpdateDto): Promise<PartnerResponseDto> {
    await this.requireAccess({ auth, permission: Permission.PartnerUpdate, ids: [sharedById] });
    const partnerId: PartnerIds = { sharedById, sharedWithId: auth.user.id };

View on GitHub (pinned to 199723261c)

Solutions

  1. Refresh the partner list before issuing the delete.
  2. Treat 400 'Partner not found' as already-removed success in idempotent delete flows.
  3. Guard the UI so users can only remove partners present in the current list.

Example fix

// before
const partner = await this.partnerRepository.get(partnerId);
if (!partner) {
  throw new BadRequestException('Partner not found');
}

// after (DELETE is idempotent)
const partner = await this.partnerRepository.get(partnerId);
if (!partner) {
  return;
}
Defensive patterns

Strategy: validation

Validate before calling

// Confirm the partnership exists before issuing DELETE.
const partners = await partnerService.search(auth, { direction: 'shared-by' });
if (!partners.some((p) => p.id === sharedWithId)) return; // nothing to remove

Type guard

const isPartner = (p: PartnerResponseDto | null | undefined): p is PartnerResponseDto =>
  !!p && typeof p.id === 'string';

Try / catch

try {
  await partnerService.remove(auth, sharedWithId);
} catch (e) {
  if (e instanceof BadRequestException && /not found/i.test(e.message)) {
    // already removed: idempotent success
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: DELETE /partner/{sharedWithId} for a sharedWithId that is not currently a partner of the caller; double-delete after the share was already removed.

Common situations: UI state out of sync with server (a partner was removed elsewhere); client retries a delete that already succeeded; user removed from a stale list view.

Related errors


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