{"record":{"id":"79fb398c7e3126ea","repo":"calcom/cal.diy","slug":"failed-to-update-private-link","errorCode":null,"errorMessage":"Failed to update private link","messagePattern":"Failed to update private link","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"critical","filePath":"apps/api/v2/src/platform/event-types-private-links/services/private-links.service.ts","lineNumber":101,"sourceCode":"      if (!updated) throw new NotFoundException(\"Updated link not found\");\n      const mapped: PrivateLinkData = {\n        id: updated.link,\n        eventTypeId,\n        isExpired: isLinkExpired(updated as any),\n        bookingUrl: `${process.env.NEXT_PUBLIC_WEBAPP_URL || \"https://cal.com\"}/d/${updated.link}`,\n        expiresAt: updated.expiresAt ?? null,\n        maxUsageCount: updated.maxUsageCount ?? null,\n        usageCount: updated.usageCount ?? 0,\n      };\n      return this.outputService.transformToOutput(mapped);\n    } catch (error) {\n      if (error instanceof Error) {\n        if (error.message.includes(\"not found\")) {\n          throw new NotFoundException(error.message);\n        }\n        throw new BadRequestException(error.message);\n      }\n      throw new BadRequestException(\"Failed to update private link\");\n    }\n  }\n\n  async deletePrivateLink(eventTypeId: number, linkId: string): Promise<void> {\n    try {\n      const { count } = await this.repo.delete(eventTypeId, linkId);\n      if (count === 0) {\n        throw new NotFoundException(\"Deleted link not found\");\n      }\n    } catch (error) {\n      if (error instanceof Error) {\n        if (error.message.includes(\"not found\")) {\n          throw new NotFoundException(error.message);\n        }\n        throw new BadRequestException(error.message);\n      }\n      throw new BadRequestException(\"Failed to delete private link\");\n    }","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/apps/api/v2/src/platform/event-types-private-links/services/private-links.service.ts#L83-L119","documentation":"Fallback BadRequestException thrown by PrivateLinksService.updatePrivateLink when the caught value is NOT an instance of Error. This is the defensive branch for code that throws a non-Error value (string, plain object, null) somewhere in the update pipeline. It masks the original cause behind a generic message, so debugging requires reproducing the throw shape.","triggerScenarios":"A downstream dependency does `throw 'string error'` or `throw { code: 123 }` instead of `throw new Error(...)`; a Prisma or library bug throws a non-Error; an assertion library configured to throw raw values; a Promise rejection with a non-Error reason.","commonSituations":"Third-party middleware or a custom repository method that rejects with a string; an older Node.js library that throws primitives; a bug in outputService.transformToOutput that throws a plain object. This is almost always a defect in the service's own dependency tree, not a client error.","solutions":["Search the repo for `throw` statements that throw non-Error values (e.g. `throw \"` or `throw {`) and convert them to `throw new Error(...)`.","Reproduce locally with debug logging in the catch block to print `typeof error` and the value, then trace it to its source.","If originating from a library, wrap its calls so errors are normalized to Error instances before reaching this service.","File an issue: a generic 400 here hides the real failure; the service should log the raw caught value before falling back."],"exampleFix":"// before (in some downstream repo)\nif (!row) throw 'link missing';\n// after\nif (!row) throw new Error('link missing');","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Client cannot prevent a non-Error throw on the server; catch and escalate\ntry {\n  await api.patch(url, payload);\n} catch (e) {\n  if (e.response?.status === 400 && e.response?.data?.message === 'Failed to update private link') {\n    // opaque server-side defect; retry once, then surface to ops\n  } else throw e;\n}","preventionTips":["Report this to the server team — a generic 400 here is a defect, not a client error.","Pin your dependency versions so transitive upgrades do not introduce non-Error throws.","Add an integration test that asserts the service rejects bad input with a specific message, not the fallback."],"tags":["nestjs","bad-request","defensive-catch","private-links","error-handling"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}