calcom/cal.diy · critical · InternalServerErrorException

Could not set ${app} as default conferencing app

Error message

Could not set ${app} as default conferencing app

What it means

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.

Source

Thrown at apps/api/v2/src/modules/conferencing/services/conferencing.service.ts:123

  async disconnectConferencingApp(user: UserWithProfile, app: string) {
    const credential = await this.checkAppIsValidAndConnected(user, app);
    return handleDeleteCredential({
      userId: user.id,
      userMetadata: user?.metadata,
      credentialId: credential.id,
    });
  }

  async setDefaultConferencingApp(user: UserWithProfile, app: string) {
    // cal-video is global, so we can skip this check
    if (app !== CAL_VIDEO) {
      await this.checkAppIsValidAndConnected(user, app);
    }
    const updatedUser = await this.usersRepository.setDefaultConferencingApp(user.id, app);
    const metadata = updatedUser.metadata as { defaultConferencingApp?: { appSlug?: string } };
    if (metadata?.defaultConferencingApp?.appSlug !== app) {
      throw new InternalServerErrorException(`Could not set ${app} as default conferencing app`);
    }
    return true;
  }

  async generateOAuthUrl(app: string, state: OAuthCallbackState) {
    switch (app) {
      case ZOOM:
        return await this.zoomVideoService.generateZoomAuthUrl(JSON.stringify(state));

      case OFFICE_365_VIDEO:
        return await this.office365VideoService.generateOffice365AuthUrl(JSON.stringify(state));

      default:
        throw new BadRequestException(
          "Invalid conferencing app, available apps are: ",
          [ZOOM, OFFICE_365_VIDEO].join(", ")
        );
    }

View on GitHub (pinned to 176037d0af)

Solutions

  1. Retry the request once — if it was a race, the second call usually verifies cleanly.
  2. Inspect the user metadata row after the failure: SELECT metadata->'defaultConferencingApp' FROM users WHERE id=? — confirm the actual shape.
  3. Ensure no concurrent setDefaultConferencingApp calls (client-side lock or queue).
  4. If reproducible, audit usersRepository.setDefaultConferencingApp for the exact write logic and any metadata parse/stringify that drops fields.

Example fix

// before: service reads back and compares
const updatedUser = await this.usersRepository.setDefaultConferencingApp(user.id, app);
if (metadata?.defaultConferencingApp?.appSlug !== app) {
  throw new InternalServerErrorException(`Could not set ${app} as default conferencing app`);
}

// after: log the mismatch shape to aid diagnosis
if (metadata?.defaultConferencingApp?.appSlug !== app) {
  this.logger.error('defaultConferencingApp write did not verify', { userId: user.id, requested: app, actual: metadata });
  throw new InternalServerErrorException(`Could not set ${app} as default conferencing app`, { cause: { actual: metadata } });
}
Defensive patterns

Strategy: retry

Validate before calling

// Pre-flight: confirm no concurrent default-app write is in progress (advisory lock).
async function acquireDefaultAppLock(userId: number): Promise<boolean> {
  // pseudo: SETNX lock key in Redis
  return redis.set(`default-app-lock:${userId}`, '1', 'NX', 'EX', 5) === 'OK';
}

if (!(await acquireDefaultAppLock(userId))) {
  throw new Error('Another default-app update is in progress. Retry shortly.');
}

Type guard

function defaultAppWriteVerified(updated: { metadata?: { defaultConferencingApp?: { appSlug?: string } } }, expected: string): boolean {
  return updated?.metadata?.defaultConferencingApp?.appSlug === expected;
}

Try / catch

async function setDefaultWithRetry(user: UserWithProfile, app: string, attempts = 2): Promise<void> {
  try {
    await conferencingService.setDefaultConferencingApp(user, app);
  } catch (e) {
    if (e instanceof InternalServerErrorException && attempts > 0) {
      await new Promise(r => setTimeout(r, 200));
      return setDefaultWithRetry(user, app, attempts - 1);
    }
    throw e;
  }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12). Data as JSON: /api/errors/f3e5fc249317ab78. Report an issue: GitHub.