immich-app/immich · error · BadRequestException

Asset not in stack

Error message

Asset not in stack

What it means

Immich throws this BadRequestException when attempting to remove an asset from a stack that does not actually contain it — the stack fetched for the asset (getForAssetRemoval) is missing or its id differs from the target stackId in the request.

Solutions

  1. Confirm the asset is stacked and note its actual stackId (GET /asset/:id or asset.stackedAssets).
  2. Call the endpoint with the stack id that actually contains the asset.
  3. Refresh stack data before retrying if another client may have changed stacking.
  4. Check argument order — dto takes { id (stack), assetId } — and ensure they aren't swapped.

Example fix

// before
await api.removeAssetFromStack(auth, oldStackId, assetId);
// after
const asset = await api.getAssetInfo(auth, assetId);
const actualStackId = asset.stack?.id ?? asset.stackId;
await api.removeAssetFromStack(auth, actualStackId, assetId);
Defensive patterns

Strategy: validation

Validate before calling

const asset = await api.getAssetInfo(auth, assetId);
if (asset.stackId !== stackId) throw new Error(`Asset ${assetId} is not in stack ${stackId}`);

Type guard

const assetIsInStack = (asset: { stackId?: string | null }, stackId: string) => asset.stackId === stackId;

Try / catch

try {
  await api.removeAssetFromStack(auth, stackId, assetId);
} catch (e) {
  if (e.status === 400 && /Asset not in stack/.test(e.message)) {
    // refetch asset to get its real stackId, then retry
  } else throw e;
}

Prevention

When it happens

Trigger: DELETE /stacks/:id/assets with an assetId whose actual stack differs from the :id path parameter, the asset is not stacked at all, or ids are swapped/mistyped.

Common situations: Client using a stale stack id after the asset was moved to another stack; passing (stackId, assetId) in the wrong order; concurrent unstacking by another client or job.

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


AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15). Data as JSON: /api/errors/de50fa3fb471cf54. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/stack.service.ts:70

    await this.requireAccess({ auth, permission: Permission.StackDelete, ids: [id] });
    await this.stackRepository.delete(id);
    await this.eventRepository.emit('StackDelete', { stackId: id, userId: auth.user.id });
  }

  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 e55ac299a4)