{"record":{"id":"36330013d3159d9e","repo":"immich-app/immich","slug":"cannot-remove-stack-s-primary-asset","errorCode":null,"errorMessage":"Cannot remove stack's primary asset","messagePattern":"Cannot remove stack's primary asset","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"warning","filePath":"server/src/services/stack.service.ts","lineNumber":74,"sourceCode":"\n  async deleteAll(auth: AuthDto, dto: BulkIdsDto): Promise<void> {\n    await this.requireAccess({ auth, permission: Permission.StackDelete, ids: dto.ids });\n    await this.stackRepository.deleteAll(dto.ids);\n    await this.eventRepository.emit('StackDeleteAll', { stackIds: dto.ids, userId: auth.user.id });\n  }\n\n  async removeAsset(auth: AuthDto, dto: UUIDAssetIDParamDto): Promise<void> {\n    const { id: stackId, assetId } = dto;\n    await this.requireAccess({ auth, permission: Permission.StackUpdate, ids: [stackId] });\n\n    const stack = await this.stackRepository.getForAssetRemoval(assetId);\n\n    if (!stack?.id || stack.id !== stackId) {\n      throw new BadRequestException('Asset not in stack');\n    }\n\n    if (stack.primaryAssetId === assetId) {\n      throw new BadRequestException(\"Cannot remove stack's primary asset\");\n    }\n\n    await this.assetRepository.update({ id: assetId, stackId: null });\n    await this.eventRepository.emit('StackUpdate', { stackId, userId: auth.user.id });\n  }\n\n  private findOrFail(id: string) {\n    return findOrFail(() => this.stackRepository.getById(id), 'Asset stack');\n  }\n}\n","sourceCodeStart":56,"sourceCodeEnd":85,"githubUrl":"https://github.com/immich-app/immich/blob/199723261c6ffa897fec8ccdaea6359e39c37cc3/server/src/services/stack.service.ts#L56-L85","documentation":"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.","triggerScenarios":"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.","commonSituations":"Bulk-removing assets including the primary, or a UI that lets users remove any asset without first reassigning the primary.","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."],"exampleFix":"// before\nawait removeAsset({ id: stackId, assetId: stack.primaryAssetId });\n// after - reassign primary, then remove\nawait update(stackId, { primaryAssetId: otherMemberId });\nawait removeAsset({ id: stackId, assetId: oldPrimaryId });","handlingStrategy":"validation","validationCode":"const stack = await stackApi.get(id);\nif (stack.primaryAssetId === assetId) {\n  const otherMember = stack.assets.find((a) => a.id !== assetId);\n  if (!otherMember) throw new Error('Cannot remove the only asset; delete the stack instead.');\n  await stackApi.update(id, { primaryAssetId: otherMember.id });\n}\nawait stackApi.removeAsset({ id, assetId });","typeGuard":"const isPrimaryAsset = (assetId: string, stack: { primaryAssetId: string }): boolean =>\n  stack.primaryAssetId === assetId;","tryCatchPattern":"try {\n  await stackApi.removeAsset({ id: stackId, assetId });\n} catch (e) {\n  if (e instanceof BadRequestException && /primary asset/i.test(e.message)) {\n    // reassign primary first, then retry\n    const other = stack.assets.find((a) => a.id !== assetId);\n    if (other) {\n      await stackApi.update(stackId, { primaryAssetId: other.id });\n      return stackApi.removeAsset({ id: stackId, assetId });\n    }\n  }\n  throw e;\n}","preventionTips":["Disable remove on the primary asset, or auto-suggest a new primary.","To dissolve a stack, delete it instead of removing the primary."],"tags":["stack","asset","validation"],"backgroundTag":null,"analyzedSha":"199723261c6ffa897fec8ccdaea6359e39c37cc3","analyzedAt":"2026-08-12T04:54:27.085Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}