immich-app/immich · warning · BadRequestException
Cannot remove stack's primary asset
Error message
Cannot remove stack's primary asset
What it means
StackService.removeAsset() refuses to remove the stack's primary asset: if stack.primaryAssetId === assetId it throws BadRequestException "Cannot remove stack's primary asset" (stack.service.ts:74, HTTP 400). The primary defines the stack's cover/representative and must be reassigned first.
Source
Thrown at server/src/services/stack.service.ts:74
async deleteAll(auth: AuthDto, dto: BulkIdsDto): Promise<void> {
await this.requireAccess({ auth, permission: Permission.StackDelete, ids: dto.ids });
await this.stackRepository.deleteAll(dto.ids);
await this.eventRepository.emit('StackDeleteAll', { stackIds: dto.ids, userId: auth.user.id });
}
async removeAsset(auth: AuthDto, dto: UUIDAssetIDParamDto): Promise<void> {
const { id: stackId, assetId } = dto;
await this.requireAccess({ auth, permission: Permission.StackUpdate, ids: [stackId] });
const stack = await this.stackRepository.getForAssetRemoval(assetId);
if (!stack?.id || stack.id !== stackId) {
throw new BadRequestException('Asset not in stack');
}
if (stack.primaryAssetId === assetId) {
throw new BadRequestException("Cannot remove stack's primary asset");
}
await this.assetRepository.update({ id: assetId, stackId: null });
await this.eventRepository.emit('StackUpdate', { stackId, userId: auth.user.id });
}
private findOrFail(id: string) {
return findOrFail(() => this.stackRepository.getById(id), 'Asset stack');
}
}
View on GitHub (pinned to 199723261c)
Solutions
- First PATCH /stacks/:id to set primaryAssetId to a different member, then remove the old primary.
- In the UI, disable remove on the primary asset or prompt to reassign primary first.
- If you intend to dissolve the stack, DELETE /stacks/:id instead.
Example fix
// before
await removeAsset({ id: stackId, assetId: stack.primaryAssetId });
// after - reassign primary, then remove
await update(stackId, { primaryAssetId: otherMemberId });
await removeAsset({ id: stackId, assetId: oldPrimaryId }); Defensive patterns
Strategy: validation
Validate before calling
const stack = await stackApi.get(id);
if (stack.primaryAssetId === assetId) {
const otherMember = stack.assets.find((a) => a.id !== assetId);
if (!otherMember) throw new Error('Cannot remove the only asset; delete the stack instead.');
await stackApi.update(id, { primaryAssetId: otherMember.id });
}
await stackApi.removeAsset({ id, assetId }); Type guard
const isPrimaryAsset = (assetId: string, stack: { primaryAssetId: string }): boolean =>
stack.primaryAssetId === assetId; Try / catch
try {
await stackApi.removeAsset({ id: stackId, assetId });
} catch (e) {
if (e instanceof BadRequestException && /primary asset/i.test(e.message)) {
// reassign primary first, then retry
const other = stack.assets.find((a) => a.id !== assetId);
if (other) {
await stackApi.update(stackId, { primaryAssetId: other.id });
return stackApi.removeAsset({ id: stackId, assetId });
}
}
throw e;
} Prevention
- Disable remove on the primary asset, or auto-suggest a new primary.
- To dissolve a stack, delete it instead of removing the primary.
When it happens
Trigger: DELETE /stacks/:id/assets/:assetId targeting the asset that is currently the stack's primaryAssetId. The asset-in-stack check (176) passes first, then this guard fires.
Common situations: Bulk-removing assets including the primary, or a UI that lets users remove any asset without first reassigning the primary.
Related errors
- Primary asset must be in the stack
- Asset not in stack
- May not request original file
- Asset not found or asset is not a video
- Both assets must exist
AI-assisted analysis of immich-app/immich@199723261c (2026-08-12).
Data as JSON: /api/errors/36330013d3159d9e.
Report an issue: GitHub.