{"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":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","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/e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c/server/src/services/stack.service.ts#L56-L85","documentation":"Immich throws this BadRequestException when removing an asset from a stack if that asset is the stack's primary asset. The primary asset anchors the stack and must be removed via unstacking or by first promoting another asset.","triggerScenarios":"DELETE /stacks/:id/assets where assetId === stack.primaryAssetId.","commonSituations":"Bulk removal scripts iterating all stack assets including the cover; UI defaulting to the first (primary) asset; trying to dissolve a stack one asset at a time starting with the primary.","solutions":["Choose a different asset in the stack as the removal target, or update the stack's primaryAssetId to another member first, then remove this asset.","To remove the whole grouping, use DELETE /stacks/:id (unstack all) instead of removing the primary asset individually.","Skip primary assets in bulk-removal loops.","Handle the 400 by fetching the stack and testing primaryAssetId before the call."],"exampleFix":"// before\nawait api.removeAssetFromStack(auth, stackId, stack.primaryAssetId);\n// after\nawait api.updateStack(auth, stackId, { primaryAssetId: stack.assets.find(a => a.id !== stack.primaryAssetId).id });\nawait api.removeAssetFromStack(auth, stackId, originalPrimaryAssetId);","handlingStrategy":"validation","validationCode":"const stack = await api.getStack(auth, stackId);\nif (stack.primaryAssetId === assetId) throw new Error('Promote another asset to primary before removing this one');","typeGuard":"const isPrimaryAsset = (stack: { primaryAssetId: string }, assetId: string) => stack.primaryAssetId === assetId;","tryCatchPattern":"try {\n  await api.removeAssetFromStack(auth, stackId, assetId);\n} catch (e) {\n  if (e.status === 400 && /primary asset/.test(e.message)) {\n    await api.updateStack(auth, stackId, { primaryAssetId: otherAssetId });\n    await api.removeAssetFromStack(auth, stackId, assetId);\n  } else throw e;\n}","preventionTips":["Exclude primaryAssetId from bulk removal lists","Promote a replacement primary before removing the cover asset","Use full unstack (delete stack) when dissolving a group","Check primaryAssetId before any per-asset stack mutation"],"tags":["immich","stacks","bad-request","precondition"],"backgroundTag":"invalid-state-transition","analyzedSha":"e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c","analyzedAt":"2026-09-15T07:20:19.675Z","contentChangedAt":"2026-09-15T07:20:19.675Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}