immich-app/immich · error · BadRequestException

Primary asset must be in the stack

Error message

Primary asset must be in the stack

What it means

Immich throws this BadRequestException in StackService.update when the requested primaryAssetId is not one of the assets belonging to the stack. The primary asset must be a member of the stack being updated.

Solutions

  1. Fetch the stack (GET /stacks/:id) and pick primaryAssetId from its assets array.
  2. Verify the asset's stackId matches the stack before updating.
  3. If the asset should join the stack first, add it to the stack, then set it as primary.
  4. Refresh client-side stack data before retrying to clear stale ids.

Example fix

// before
await api.updateStack(auth, stackId, { primaryAssetId: someAssetId });
// after
const stack = await api.getStack(auth, stackId);
if (stack.assets.some(a => a.id === someAssetId)) {
  await api.updateStack(auth, stackId, { primaryAssetId: someAssetId });
}
Defensive patterns

Strategy: validation

Validate before calling

const stack = await api.getStack(auth, stackId);
if (!stack.assets.some(a => a.id === primaryAssetId)) throw new Error('primaryAssetId must be a member of the stack');

Type guard

const isInStack = (stack: { assets: { id: string }[] }, assetId: string) =>
  stack.assets.some(a => a.id === assetId);

Try / catch

try {
  await api.updateStack(auth, stackId, { primaryAssetId });
} catch (e) {
  if (e.status === 400 && /Primary asset must be in the stack/.test(e.message)) {
    // refetch stack, pick a valid asset id, retry
  } else throw e;
}

Prevention

When it happens

Trigger: PUT /stacks/:id with a primaryAssetId that references an asset not in the stack (wrong stack id, asset unstacked earlier, or an id from a different stack).

Common situations: Clients caching asset ids then the stack was modified concurrently; UI sending the currently-viewed asset's id instead of one inside the stack; bulk scripts pairing stack ids and asset ids from different sources.

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/637b04b69ec7a0f0. Report an issue: GitHub.

Appendix: source

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

    const stack = await this.stackRepository.create({ ownerId: auth.user.id }, dto.assetIds);

    await this.eventRepository.emit('StackCreate', { stackId: stack.id, userId: auth.user.id });

    return mapStack(stack, { auth });
  }

  async get(auth: AuthDto, id: string): Promise<StackResponseDto> {
    await this.requireAccess({ auth, permission: Permission.StackRead, ids: [id] });
    const stack = await this.findOrFail(id);
    return mapStack(stack, { auth });
  }

  async update(auth: AuthDto, id: string, dto: StackUpdateDto): Promise<StackResponseDto> {
    await this.requireAccess({ auth, permission: Permission.StackUpdate, ids: [id] });
    const stack = await this.findOrFail(id);
    if (dto.primaryAssetId && stack.assets.every(({ id }) => id !== dto.primaryAssetId)) {
      throw new BadRequestException('Primary asset must be in the stack');
    }

    const updatedStack = await this.stackRepository.update(id, { id, primaryAssetId: dto.primaryAssetId });

    await this.eventRepository.emit('StackUpdate', { stackId: id, userId: auth.user.id });

    return mapStack(updatedStack, { auth });
  }

  async delete(auth: AuthDto, id: string): Promise<void> {
    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);

View on GitHub (pinned to e55ac299a4)