immich-app/immich · warning · BadRequestException
Cannot merge a person into themselves
Error message
Cannot merge a person into themselves
What it means
mergePeople rejects an ids array containing duplicates, since merging a person into itself is a no-op and would corrupt the bulk-id response contract. It compares Set size to array length and throws BadRequestException('Cannot merge a person into themselves') when they differ.
Solutions
- De-duplicate the ids array before calling: [...new Set(ids)].
- Exclude the primary person's ID from the ids payload; the URL already identifies the primary.
- Fix selection UI to prevent picking the same person twice.
- If you intended to change the primary person, use the person update endpoint instead of merge.
Example fix
// before
await api.peopleApi.mergePerson({ id: primaryId, mergePersonDto: { ids: [dupA, dupA, primaryId] } });
// after
const ids = [...new Set([dupA, dupA, primaryId])].filter((id) => id !== primaryId);
await api.peopleApi.mergePerson({ id: primaryId, mergePersonDto: { ids } }); Defensive patterns
Strategy: validation
Validate before calling
const deduped = [...new Set(ids)];
if (deduped.length !== ids.length) throw new Error('Duplicate person IDs in merge request'); Type guard
function hasNoDuplicates(ids) {
return Array.isArray(ids) && new Set(ids).size === ids.length;
} Try / catch
try {
await api.peopleApi.mergePerson({ id: primaryId, mergePersonDto: { ids } });
} catch (e) {
if (e.status === 400 && String(e.message).includes('merge a person into themselves')) {
ids = [...new Set(ids)].filter((id) => id !== primaryId); // retry once deduped
} else throw e;
} Prevention
- Always deduplicate ids with Set before merging.
- Exclude the primary person's ID from the body payload.
- Use single-select components that cannot add the same person twice.
When it happens
Trigger: POST merge with duplicate IDs in the ids array — e.g. ids: ["a", "a"], or the primary person's own ID repeated in the body list.
Common situations: Client selection logic adding the same person twice (double-click, not de-duplicating), or scripts merging a person into itself thinking it renames/keeps the primary.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- At least two people are required for merging
- Duplicate items are not allowed
- A tag with that name already exists
- Asset dimensions are not available for editing
- assetIds, albumId, or userId is required
AI-assisted analysis of immich-app/immich@f48d4b3321 (2026-09-15).
Data as JSON: /api/errors/bbdbd8880f6413dd.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/person.service.ts:593
@OnJob({ name: JobName.PersonFileMigration, queue: QueueName.Migration })
async handlePersonMigration({ ownerId, personGroupId }: JobOf<JobName.PersonFileMigration>): Promise<JobStatus> {
const person = await this.personRepository.getByGroupId({ ownerId, personGroupId });
if (!person) {
return JobStatus.Failed;
}
await this.storageCore.movePersonFile(person, PersonPathType.Face);
return JobStatus.Success;
}
async mergePeople(auth: AuthDto, { ids }: MergePersonDto): Promise<BulkIdResponseDto[]> {
if (ids.length < 2) {
throw new BadRequestException('At least two people are required for merging');
}
if (new Set(ids).size !== ids.length) {
throw new BadRequestException('Cannot merge a person into themselves');
}
const results: BulkIdResponseDto[] = [];
const allowedIds = await this.checkAccess({ auth, permission: Permission.PersonMerge, ids });
const peopleMap: Record<string, Selectable<PersonTable>[]> = {};
for (const mergePerson of await this.personRepository.getForMergePerson(ids)) {
if (!peopleMap[mergePerson.personGroupId]) {
peopleMap[mergePerson.personGroupId] = [];
}
peopleMap[mergePerson.personGroupId].push(mergePerson);
}
const targetPeople: Record<string, Selectable<PersonTable>> = {};
for (const mergeId of ids) {
const hasAccess = allowedIds.has(mergeId);View on GitHub (pinned to f48d4b3321)