immich-app/immich · warning · BadRequestException
Partner already exists
Error message
Partner already exists
What it means
Thrown by PartnerService.create when partnerRepository.get({ sharedById, sharedWithId }) already returns a row. Represents an idempotency violation: the authenticated user is already sharing their library with the requested sharedWithId. BadRequestException -> HTTP 400.
Source
Thrown at server/src/services/partner.service.ts:16
import { BadRequestException, Injectable } from '@nestjs/common';
import { Partner } from 'src/database';
import { AuthDto } from 'src/dtos/auth.dto';
import { PartnerCreateDto, PartnerResponseDto, PartnerSearchDto, PartnerUpdateDto } from 'src/dtos/partner.dto';
import { mapUser } from 'src/dtos/user.dto';
import { Permission } from 'src/enum';
import { PartnerDirection, PartnerIds } from 'src/repositories/partner.repository';
import { BaseService } from 'src/services/base.service';
@Injectable()
export class PartnerService extends BaseService {
async create(auth: AuthDto, { sharedWithId }: PartnerCreateDto): Promise<PartnerResponseDto> {
const partnerId: PartnerIds = { sharedById: auth.user.id, sharedWithId };
const exists = await this.partnerRepository.get(partnerId);
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');
}View on GitHub (pinned to 199723261c)
Solutions
- Treat 400 'Partner already exists' as success if the goal is to ensure the share exists (idempotent create).
- Guard the UI: fetch the existing partner list first and disable already-shared users.
- Catch the 400 client-side and refresh the partner list instead of retrying the POST.
Example fix
// before
const exists = await this.partnerRepository.get(partnerId);
if (exists) {
throw new BadRequestException(`Partner already exists`);
}
// after (return the existing row so the endpoint is idempotent)
const exists = await this.partnerRepository.get(partnerId);
if (exists) {
return this.mapPartner(exists, PartnerDirection.SharedBy);
} Defensive patterns
Strategy: validation
Validate before calling
// Before creating, check the existing partnership for this caller.
const existing = await partnerService.search(auth, { direction: 'shared-by' });
if (existing.some((p) => p.id === sharedWithId)) {
// already shared; treat as success, do not POST /partner again
return existing.find((p) => p.id === sharedWithId)!;
} Type guard
const isPartner = (p: PartnerResponseDto | null | undefined): p is PartnerResponseDto => !!p && typeof p.id === 'string';
Try / catch
try {
await partnerService.create(auth, { sharedWithId });
} catch (e) {
if (e instanceof BadRequestException && /already exists/i.test(e.message)) {
// idempotent success
return;
}
throw e;
} Prevention
- Fetch the current partner list before showing the share UI.
- Make create idempotent on the client by treating 'already exists' as success.
- Disable the share button for users already in the partner list.
When it happens
Trigger: POST /partner with a sharedWithId that the caller already shares with; double submission of the same partner-create form; a UI that re-issues the request on retry.
Common situations: User clicks 'Share' twice; client retries after a timeout even though the first call succeeded; race condition where two parallel requests both pass the exists-check before either inserts.
Related errors
- Partner not found
- Invalid user
- Can't delete a missing profile Image
- Unsupported file type ${filename}
- May not request original file
AI-assisted analysis of immich-app/immich@199723261c (2026-08-12).
Data as JSON: /api/errors/955cf7765d772a70.
Report an issue: GitHub.