immich-app/immich · warning · BadRequestException
Partner already exists
Error message
Partner already exists
What it means
PartnerService.create rejects the request when a partner record for the exact pair (sharedById=caller, sharedWithId=target user) already exists, since the partner relation is unique per ordered pair. It throws BadRequestException before creating a duplicate row.
Solutions
- Check existing partners (GET /api/partners) before creating; treat an existing record as success.
- Use a different target user if a different share was intended.
- Delete the existing partner record (DELETE /api/partners/{id}) if it should be recreated.
- Wrap creation client-side to handle 400 'Partner already exists' as an idempotent no-op.
Example fix
// before
await api.partnersApi.createPartner({ sharedWithId: userId }); // throws if duplicate
// after
const existing = await api.partnersApi.getPartners({ direction: 'shared-by' });
if (!existing.some((p) => p.userId === userId)) {
await api.partnersApi.createPartner({ sharedWithId: userId });
} Defensive patterns
Strategy: validation
Validate before calling
const existing = await api.partnersApi.getPartners({ direction: 'shared-by' });
const alreadyPartner = existing.some((p) => p.userId === sharedWithId);
if (alreadyPartner) return; // idempotent no-op Try / catch
try {
await api.partnersApi.createPartner({ sharedWithId });
} catch (e) {
if (e.status === 400 && String(e.message).includes('Partner already exists')) return; // treat as success
throw e;
} Prevention
- Debounce/disable the share button while a request is in flight.
- Check the partner list before offering share options.
- Treat create as idempotent in clients.
When it happens
Trigger: POST /api/partners with a sharedWithId for which the calling user already has an existing partner record (either direction previously created).
Common situations: Double-clicking the share button in the UI, replaying an API request, sharing with the same user from two clients, or retrying after a timeout where the first request actually succeeded.
Understand the failure class
Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.
Related errors
- Can't delete a missing profile Image
- Email is not available
- Invalid user
- Partner not found
- A tag with that name already exists
AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15).
Data as JSON: /api/errors/955cf7765d772a70.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/partner.service.ts:16
import { BadRequestException, Injectable } from '@nestjs/common';
import { Partner } from 'src/database.js';
import { AuthDto } from 'src/dtos/auth.dto.js';
import { PartnerCreateDto, PartnerResponseDto, PartnerSearchDto, PartnerUpdateDto } from 'src/dtos/partner.dto.js';
import { mapUser } from 'src/dtos/user.dto.js';
import { Permission } from 'src/enum.js';
import { PartnerDirection, PartnerIds } from 'src/repositories/partner.repository.js';
import { BaseService } from 'src/services/base.service.js';
@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 e55ac299a4)