{"record":{"id":"f3e5fc249317ab78","repo":"calcom/cal.diy","slug":"could-not-set-app-as-default-conferencing-app","errorCode":null,"errorMessage":"Could not set ${app} as default conferencing app","messagePattern":"Could not set (.+?) as default conferencing app","errorType":"http","errorClass":"InternalServerErrorException","httpStatus":500,"severity":"critical","filePath":"apps/api/v2/src/modules/conferencing/services/conferencing.service.ts","lineNumber":123,"sourceCode":"\n  async disconnectConferencingApp(user: UserWithProfile, app: string) {\n    const credential = await this.checkAppIsValidAndConnected(user, app);\n    return handleDeleteCredential({\n      userId: user.id,\n      userMetadata: user?.metadata,\n      credentialId: credential.id,\n    });\n  }\n\n  async setDefaultConferencingApp(user: UserWithProfile, app: string) {\n    // cal-video is global, so we can skip this check\n    if (app !== CAL_VIDEO) {\n      await this.checkAppIsValidAndConnected(user, app);\n    }\n    const updatedUser = await this.usersRepository.setDefaultConferencingApp(user.id, app);\n    const metadata = updatedUser.metadata as { defaultConferencingApp?: { appSlug?: string } };\n    if (metadata?.defaultConferencingApp?.appSlug !== app) {\n      throw new InternalServerErrorException(`Could not set ${app} as default conferencing app`);\n    }\n    return true;\n  }\n\n  async generateOAuthUrl(app: string, state: OAuthCallbackState) {\n    switch (app) {\n      case ZOOM:\n        return await this.zoomVideoService.generateZoomAuthUrl(JSON.stringify(state));\n\n      case OFFICE_365_VIDEO:\n        return await this.office365VideoService.generateOffice365AuthUrl(JSON.stringify(state));\n\n      default:\n        throw new BadRequestException(\n          \"Invalid conferencing app, available apps are: \",\n          [ZOOM, OFFICE_365_VIDEO].join(\", \")\n        );\n    }","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/apps/api/v2/src/modules/conferencing/services/conferencing.service.ts#L105-L141","documentation":"Thrown by ConferencingService.setDefaultConferencingApp as an InternalServerErrorException (HTTP 500) when usersRepository.setDefaultConferencingApp returns a user whose metadata.defaultConferencingApp.appSlug does not match the requested app. This is a post-write verification that catches DB write failures, race conditions, or metadata-shape regressions. Unlike the 400s in this file, this indicates a server-side fault, not a client error.","triggerScenarios":"A concurrent setDefaultConferencingApp call overwrote metadata between the write and the read-back; userMetadata serialization strips the appSlug; a DB trigger or Prisma middleware mutates metadata; the upsert partially failed.","commonSituations":"Two browser tabs setting default apps simultaneously; metadata schema drift after an upgrade; a custom Prisma extension modifying metadata on update; transient DB error masked by a non-throwing partial write.","solutions":["Retry the request once — if it was a race, the second call usually verifies cleanly.","Inspect the user metadata row after the failure: SELECT metadata->'defaultConferencingApp' FROM users WHERE id=? — confirm the actual shape.","Ensure no concurrent setDefaultConferencingApp calls (client-side lock or queue).","If reproducible, audit usersRepository.setDefaultConferencingApp for the exact write logic and any metadata parse/stringify that drops fields."],"exampleFix":"// before: service reads back and compares\nconst updatedUser = await this.usersRepository.setDefaultConferencingApp(user.id, app);\nif (metadata?.defaultConferencingApp?.appSlug !== app) {\n  throw new InternalServerErrorException(`Could not set ${app} as default conferencing app`);\n}\n\n// after: log the mismatch shape to aid diagnosis\nif (metadata?.defaultConferencingApp?.appSlug !== app) {\n  this.logger.error('defaultConferencingApp write did not verify', { userId: user.id, requested: app, actual: metadata });\n  throw new InternalServerErrorException(`Could not set ${app} as default conferencing app`, { cause: { actual: metadata } });\n}","handlingStrategy":"retry","validationCode":"// Pre-flight: confirm no concurrent default-app write is in progress (advisory lock).\nasync function acquireDefaultAppLock(userId: number): Promise<boolean> {\n  // pseudo: SETNX lock key in Redis\n  return redis.set(`default-app-lock:${userId}`, '1', 'NX', 'EX', 5) === 'OK';\n}\n\nif (!(await acquireDefaultAppLock(userId))) {\n  throw new Error('Another default-app update is in progress. Retry shortly.');\n}","typeGuard":"function defaultAppWriteVerified(updated: { metadata?: { defaultConferencingApp?: { appSlug?: string } } }, expected: string): boolean {\n  return updated?.metadata?.defaultConferencingApp?.appSlug === expected;\n}","tryCatchPattern":"async function setDefaultWithRetry(user: UserWithProfile, app: string, attempts = 2): Promise<void> {\n  try {\n    await conferencingService.setDefaultConferencingApp(user, app);\n  } catch (e) {\n    if (e instanceof InternalServerErrorException && attempts > 0) {\n      await new Promise(r => setTimeout(r, 200));\n      return setDefaultWithRetry(user, app, attempts - 1);\n    }\n    throw e;\n  }\n}","preventionTips":["Disable the default-app control client-side for the duration of the in-flight request.","Audit usersRepository.setDefaultConferencingApp for metadata parse/stringify round-trips that could drop fields.","Add a distributed lock around concurrent default-app writes per user.","Log the post-write metadata shape on mismatch to accelerate root-cause."],"tags":["conferencing","default-app","nestjs","internal-error","metadata","data-integrity"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}