calcom/cal.diy · error · NotFoundException
Calendar credentials not found
Error message
Calendar credentials not found
What it means
Thrown by CalendarsService.checkCalendarCredentials (calendars.service.ts:199) as NotFoundException (HTTP 404) when calendarsRepository.getCalendarCredentials(credentialId, userId) returns null — the credentialId does not exist or does not belong to the user. Used as the guard at the start of the disconnect endpoint.
Source
Thrown at apps/api/v2/src/platform/calendars/services/calendars.service.ts:199
}
const { client_id, client_secret } = this.oAuthCalendarResponseSchema.parse(app.keys);
if (!client_id) {
throw new NotFoundException();
}
if (!client_secret) {
throw new NotFoundException();
}
return { client_id, client_secret };
}
async checkCalendarCredentials(credentialId: number, userId: number) {
const credential = await this.calendarsRepository.getCalendarCredentials(credentialId, userId);
if (!credential) {
throw new NotFoundException("Calendar credentials not found");
}
}
async createAndLinkCalendarEntry(
userId: number,
externalId: string,
key: Prisma.InputJsonValue,
calendarType: keyof typeof APPS_TYPE_ID_MAPPING,
credentialId?: number | null
) {
const credential = await this.credentialsRepository.upsertUserAppCredential(
calendarType,
key,
userId,
credentialId
);
await this.selectedCalendarsRepository.upsertSelectedCalendar(View on GitHub (pinned to 176037d0af)
Solutions
- Use a credentialId sourced from GET /v2/calendars for the current user.
- Treat a 404 on disconnect as 'already disconnected' (idempotent) in the client rather than an error.
- Guard the UI: disable the disconnect button once the credential is no longer listed.
Example fix
// before: client treats 404 as fatal
try { await api.post('/v2/calendars/google_calendar/disconnect', { id: credId }); }
catch (e) { throw e; }
// after: treat 404 as already-disconnected
try { await api.post('/v2/calendars/google_calendar/disconnect', { id: credId }); }
catch (e) {
if (e.response?.status !== 404) throw e;
// already gone — refresh the list
await refreshCalendars();
} Defensive patterns
Strategy: try-catch
Validate before calling
// Verify ownership before disconnect
const { data } = await api.get('/v2/calendars');
const owned = data.connectedCalendars.some(c => c.credentialId === credentialId);
if (!owned) {
// already disconnected — nothing to do
return { status: 'already_disconnected' };
}
await api.post('/v2/calendars/google_calendar/disconnect', { id: credentialId }); Type guard
function isOwnedCredential(list, credentialId) {
return Array.isArray(list) && list.some(c => c?.credentialId === credentialId);
} Try / catch
try {
await api.post('/v2/calendars/google_calendar/disconnect', { id: credentialId });
} catch (e) {
if (e.response?.status === 404) {
// idempotent: already gone
return { status: 'already_disconnected' };
}
throw e;
} Prevention
- Source the disconnect credentialId from GET /v2/calendars, not stale client state.
- Treat 404 on disconnect as success (idempotent) in the client.
- Disable the disconnect control once the credential is no longer listed.
When it happens
Trigger: POST /v2/calendars/{calendar}/disconnect with a credentialId already deleted or owned by another user; double-submit of disconnect; client passing a stale id.
Common situations: User clicks disconnect twice; client holds a stale id after a previous disconnect; cross-user id passed accidentally.
Related errors
- Calendar connection not found
- Calendar connection not found
- Credentials for stripe not found.
- User not found
- Team with id ${teamId} not found
AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12).
Data as JSON: /api/errors/0a1852363991cf58.
Report an issue: GitHub.