{"record":{"id":"72775df132722dd9","repo":"immich-app/immich","slug":"tag-not-found","errorCode":null,"errorMessage":"Tag not found","messagePattern":"Tag not found","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"server/src/services/tag.service.ts","lineNumber":42,"sourceCode":"export class TagService extends BaseService {\n  async getAll(auth: AuthDto) {\n    const tags = await this.tagRepository.getAll(auth.user.id);\n    return tags.map((tag) => mapTag(tag));\n  }\n\n  async get(auth: AuthDto, id: string): Promise<TagResponseDto> {\n    await this.requireAccess({ auth, permission: Permission.TagRead, ids: [id] });\n    const tag = await this.findOrFail(id);\n    return mapTag(tag);\n  }\n\n  async create(auth: AuthDto, dto: TagCreateDto) {\n    let parent;\n    if (dto.parentId) {\n      await this.requireAccess({ auth, permission: Permission.TagRead, ids: [dto.parentId] });\n      parent = await this.tagRepository.get(dto.parentId);\n      if (!parent) {\n        throw new BadRequestException('Tag not found');\n      }\n    }\n\n    const userId = auth.user.id;\n    const value = parent ? `${parent.value}/${dto.name}` : dto.name;\n    const duplicate = await this.tagRepository.getByValue(userId, value);\n    if (duplicate) {\n      throw new BadRequestException(`A tag with that name already exists`);\n    }\n\n    const { color } = dto;\n    const tag = await this.tagRepository.create({ userId, value, color, parentId: parent?.id });\n\n    return mapTag(tag);\n  }\n\n  async update(auth: AuthDto, id: string, dto: TagUpdateDto): Promise<TagResponseDto> {\n    await this.requireAccess({ auth, permission: Permission.TagUpdate, ids: [id] });","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/immich-app/immich/blob/199723261c6ffa897fec8ccdaea6359e39c37cc3/server/src/services/tag.service.ts#L24-L60","documentation":"Thrown (as BadRequestException) by TagService.create when dto.parentId is supplied, the caller passes TagRead access on it, but tagRepository.get(parentId) returns null. The parent must exist before a child tag can be created, because the child's value is composed as `${parent.value}/${dto.name}`.","triggerScenarios":"POST /tags with parentId referencing a tag that was deleted, belongs to another user, or whose id is malformed.","commonSituations":"Parent tag was deleted between UI load and submit; client stored a stale parentId; the id was copy-pasted with trailing whitespace.","solutions":["Re-fetch the parent tag list and use a current parentId.","Drop parentId to create a top-level tag instead.","Verify the id format and trim whitespace before submit."],"exampleFix":"// before\nawait tagsApi.create({ name: 'summer', parentId: staleId });\n// after\nconst parent = await tagsApi.getAll().then(t => t.find(p => p.value === 'trips'))!;\nawait tagsApi.create({ name: 'summer', parentId: parent.id });","handlingStrategy":"validation","validationCode":"const parent = dto.parentId ? await tagRepository.get(dto.parentId) : null;\nif (dto.parentId && !parent) { /* show 'pick a valid parent' error, do not POST */ }","typeGuard":"function isExistingTag(t: { id: string } | null): t is { id: string } {\n  return t !== null && typeof t.id === 'string';\n}","tryCatchPattern":"try { await tagsApi.create(dto); }\ncatch (e) {\n  if (e instanceof BadRequestException && e.message === 'Tag not found') {\n    // refresh parent list, repick parentId, retry\n  }\n}","preventionTips":["Refresh the parent tag list right before showing the create form.","Disable the submit button when the selected parent is missing.","Trim and validate parentId format client-side."],"tags":["tags","validation","bad-request","hierarchy"],"backgroundTag":null,"analyzedSha":"199723261c6ffa897fec8ccdaea6359e39c37cc3","analyzedAt":"2026-08-12T04:54:27.085Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}