{"record":{"id":"a46e60f144562a8e","repo":"immich-app/immich","slug":"failed-to-save-shared-link","errorCode":null,"errorMessage":"Failed to save shared link","messagePattern":"Failed to save shared link","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"server/src/services/shared-link.service.ts","lineNumber":115,"sourceCode":"        description: dto.description || null,\n        password: dto.password,\n        expiresAt: dto.expiresAt || null,\n        allowUpload: dto.allowUpload ?? true,\n        allowDownload: dto.showMetadata === false ? false : (dto.allowDownload ?? true),\n        showExif: dto.showMetadata ?? true,\n        slug: dto.slug || null,\n      });\n\n      return mapSharedLink(sharedLink, { stripAssetMetadata: false });\n    } catch (error) {\n      this.handleError(error);\n    }\n  }\n\n  private handleError(error: unknown): never {\n    if ((error as PostgresError).constraint_name === 'shared_link_slug_uq') {\n      this.logger.debug('Shared link with this slug already exists');\n      throw new BadRequestException('Failed to save shared link');\n    }\n    throw error;\n  }\n\n  async update(auth: AuthDto, id: string, dto: SharedLinkEditDto) {\n    await this.findOrFail(auth.user.id, id);\n    try {\n      const sharedLink = await this.sharedLinkRepository.update({\n        id,\n        userId: auth.user.id,\n        description: dto.description,\n        password: dto.password,\n        expiresAt: dto.expiresAt,\n        allowUpload: dto.allowUpload,\n        allowDownload: dto.allowDownload,\n        showExif: dto.showMetadata,\n        slug: dto.slug || null,\n      });","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/immich-app/immich/blob/e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c/server/src/services/shared-link.service.ts#L97-L133","documentation":"handleError wraps shared-link writes and converts a Postgres unique-constraint violation on shared_link_slug_uq (duplicate slug/key) into BadRequestException('Failed to save shared link'); any other error is rethrown untouched. It is invoked by create and update.","triggerScenarios":"Creating or updating a shared link whose generated/edited slug collides with an existing row, violating the shared_link_slug_uq unique constraint in Postgres.","commonSituations":"Manually editing a link's slug/slug-like key to one already in use; a slug-collision in the generated short key; retries inserting the same link twice.","solutions":["Choose a different slug/key value for the link and retry.","On create, retry — the service will generate a fresh slug.","If collisions recur programmatically, generate slugs with a random suffix.","Inspect server debug logs ('Shared link with this slug already exists') to confirm the cause."],"exampleFix":"// before\nawait api.updateSharedLink(id, { slug: existingSlug });\n// after\nawait api.updateSharedLink(id, { slug: existingSlug + '-2' });","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  return await api.createSharedLink(dto);\n} catch (e) {\n  if (e.status === 400 && e.message === 'Failed to save shared link') {\n    // duplicate slug: retry with a modified slug\n    return await api.createSharedLink({ ...dto, slug: `${dto.slug}-${Date.now()}` });\n  }\n  throw e;\n}","preventionTips":["Add a random/timestamp suffix to custom slugs.","Don't reuse slugs of deleted-but-soft-retained links.","Handle the 400 distinctly from other failures — the message is intentionally generic.","Check server debug logs to confirm the slug constraint caused the failure."],"tags":["shared-link","database","unique-constraint","conflict"],"backgroundTag":"database-write-failed","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"}