App Clinic 26 min read Apr 20, 2026

App Clinic: cal.com

A dissection of the cal.com codebase: how it works, what the team does well, where the technical debt sits, and what other projects can learn from it.

What the app is

Cal.com is an open-source scheduling platform. The product handles appointment booking, calendar synchronization, and meeting management. Users create event types, share a link, and let invitees pick a time from available slots. Team accounts add round-robin assignment, collective scheduling, and workflow automations. The hosted service competes with Calendly; the same code also runs self-hosted.

The repository at calcom/cal.com is a Yarn 4 and Turborepo monorepo written in TypeScript.

Octokraft's overall health score is 70.32 (B-). The category breakdown: security 53.62, runtime 69.17, testing 34.47, code smell 81.14, dead code 94.73, consistency 98.86, duplication 100.00, compliance 100.00. The architecture review rates the codebase C overall, with modularity 62, coupling 58, scalability 68, and patterns 72.


Repository anatomy

/tmp/calcom-clinic/
├── apps/
│   ├── web/        # Next.js 16 frontend, App Router
│   └── api/
│       ├── v1/     # Legacy Express API (being phased out)
│       └── v2/     # NestJS API platform for external consumers
├── packages/
│   ├── features/   # Domain code, one directory per feature
│   ├── lib/        # Shared utilities
│   ├── app-store/  # Third-party integration adapters
│   ├── prisma/     # Database schema and generated types
│   ├── trpc/       # Type-safe API layer
│   ├── platform/   # Platform libraries for API v2
│   ├── ui/         # Shared UI components
│   ├── coss-ui/    # Shadcn-based component set
│   ├── emails/     # Transactional email templates
│   ├── embeds/     # Embeddable booker scripts
│   └── ...         # 21 packages total

The architecture documentation describes a layered dependency hierarchy: libapp-storefeaturestrpcapps/web. Lower layers must not import from higher layers. The architecture review flags violations of this rule; details appear in the architecture section.

The packages/features/ directory contains the domain code. Each feature is a vertical slice with services/, repositories/, lib/, di/, components/, and test files side-by-side. Octokraft's architecture review lists Vertical Slice Architecture as one of three structural strengths. Features include bookings/, eventtypes/, calendars/, auth/, workflows/, ee/billing/, ee/organizations/, calendar-subscription/, calAIPhone/, and pbac/.

The enterprise-edition code sits under packages/features/ee/. It carries paid-tier features: billing, seat-based subscriptions, organizations, team hierarchies, and advanced workflows. It imports from the same features layer as the open-source paths.


How the app works

A booking request passes through three layers. The browser renders an App Router page from apps/web/app/. The page is a server component that builds a legacy context object and delegates to a view from apps/web/modules/. The view calls a tRPC hook. The tRPC route in packages/trpc/server/routers/ looks up permissions, then calls a service in packages/features/. The service reads and writes through a repository that wraps Prisma. The response travels back up the same path.

The API v2 under apps/api/v2/ is a parallel entry point for external consumers. Controllers receive HTTP requests, pipes validate and transform the payload, guards check authorization, and services in the same packages/features/ layer do the work. The NestJS wiring uses @Injectable() classes, constructor injection, and @evyweb/ioctopus modules. Octokraft's architecture review lists the dependency-injection framework as a structural strength, describing it as "type-safe DI using @evyweb/ioctopus with moduleLoader pattern providing build-time safety for missing dependencies."

Data access goes through repositories. Every repository implements an interface. The NestJS side of the code receives a PrismaReadService or PrismaWriteService through the constructor and accesses Prisma via this.dbRead.prisma. The older side of the code — repositories under packages/features/*/repositories/ — often expose static methods and import a shared Prisma client directly. Octokraft's architecture review flags the static-method form as a pattern violation; the details appear in the architecture section.

Integrations with third-party services (Google Calendar, Office 365, Zoom, Stripe) live in packages/app-store/. Each integration is a directory with generated type files and a lib/CalendarService.ts or equivalent. A central adapter factory at packages/features/calendar-subscription/adapters/AdaptersFactory.ts keeps singleton instances keyed by provider name:

private singletons = {
  google_calendar: new GoogleCalendarSubscriptionAdapter(),
  office365_calendar: new Office365CalendarSubscriptionAdapter(),
} as const;

Background work runs through Trigger.dev tasks for async notifications, reminder workflows, and calendar syncs. Stripe webhooks arrive at packages/features/ee/billing/api/webhook/__handler.ts, which dispatches by event type to lazy-loaded handlers. Authentication uses NextAuth.js with the Prisma adapter.

Configuration for API v2 is loaded through a typed loadConfig() function at apps/api/v2/src/config/app.ts. The object groups settings under domain keys (api, db, stripe) and reads environment variables with inline defaults:

api: {
  port: getEnv("API_PORT", "5555"),
  keyPrefix: getEnv("API_KEY_PREFIX", "cal_"),
  usePool: getEnv("USE_POOL", "true") === "true",
}

The database is PostgreSQL, accessed through Prisma 6.16.1. Redis handles caching and rate-limiting. The test stack runs on Vitest (unit) and Playwright (end-to-end).


Architecture

Octokraft's architecture review rates the cal.com codebase C overall. The four dimensions in the review:

DimensionScore
Modularity62
Coupling58
Scalability68
Patterns72

The review's executive summary is:

Architecture review completed across 4 dimensions with overall score of 64% (320/500). Critical issues found: layer boundary violations in dependency hierarchy, god module pattern in bookings with 108 imports, calendar API rate limiting risks, and 65% of repositories bypassing dependency injection. 5 CRITICAL, 9 HIGH, and 5 MEDIUM severity issues identified requiring immediate attention.

Across the rest of Octokraft's pipeline, the split reads the same way. Consistency is 98.86. Compliance is 100.00. Duplication is 100.00. Dead code is 94.73. Security is 53.62. Testing is 34.47. The codebase is internally consistent and well-organized; the weak numbers are on the cross-cutting dimensions.

Strengths

Octokraft's architecture review lists three structural strengths.

Vertical Slice Architecture. The review describes the layout as "features organized by domain (bookings, calendars, auth) with self-contained vertical slices including services, repositories, components, and tests." In the repo, packages/features/bookings/ contains services/, repositories/, components/, lib/, di/, and Booker/ side-by-side. The same layout appears in calendars/, auth/, eventtypes/, workflows/, pbac/, and calAIPhone/. A developer working on a booking bug finds everything they need in one directory.

Dependency injection with @evyweb/ioctopus. The review calls the wiring "type-safe DI using @evyweb/ioctopus with moduleLoader pattern providing build-time safety for missing dependencies." The booking cancel service at packages/features/bookings/di/BookingCancelService.module.ts declares its dependencies through a bindModuleToClassOnToken call:

const loadModule = bindModuleToClassOnToken({
  module: thisModule,
  moduleToken,
  token,
  classs: BookingCancelService,
  depsMap: {
    userRepository: userRepositoryModuleLoader,
    bookingRepository: bookingRepositoryModuleLoader,
    profileRepository: profileRepositoryModuleLoader,
    bookingReferenceRepository: bookingReferenceRepositoryModuleLoader,
    attendeeRepository: bookingAttendeeRepositoryModuleLoader,
  },
});

A missing dependency is caught at build time by the type system.

Repository Pattern. The review calls it "well-documented repository pattern isolating Prisma ORM from business logic, enabling future ORM migration." Services depend on a repository interface, not on Prisma types. Two things fall out of that split. Tests can substitute a fake repository without mocking the database. And the Prisma dependency is contained to the repository implementations, so swapping ORMs is a bounded change instead of a codebase-wide one. The booking-guests service shows the shape:

@Injectable()
export class BookingGuestsService_2024_08_13 {
  constructor(
    private readonly bookingsRepository: BookingsRepository_2024_08_13,
    private readonly bookingsService: BookingsService_2024_08_13,
    private readonly platformBookingsService: PlatformBookingsService
  ) {}
}

Weaknesses

The review lists three cross-cutting weaknesses in the same structural review.

Layer boundary violations. The documented dependency hierarchy is libapp-storefeaturestrpcapps/web. The architecture review flags two broken directions: packages/features/ importing from packages/trpc/ (critical), and packages/lib/ importing from packages/features/ (critical). The rules are documented in the architecture file; they are not enforced by automation. The review recommends automated ESLint rules to block further drift.

Bookings as a god module. The review flags packages/features/bookings/ with 108 imports from packages/lib alone. Octokraft's module friction table shows packages/features/bookings/lib with an efferent coupling of 133 and afferent coupling of 104 — the single most connected module in the monorepo. The review recommends decomposing bookings into focused sub-modules with a target of fewer than 30 lib imports.

Static-method repositories bypassing DI. The review notes that 65% of repositories use static methods and import a shared Prisma client directly, instead of receiving a Prisma service through the constructor. Octokraft's convention detector shows the same pattern from a different angle — the "NestJS repositories receive PrismaReadService via constructor injection" convention has one conforming instance, and adapter-layer conventions sit at 66% compliance. The recommendation is to convert static repositories to instance methods with DI, which enables substitution in tests and matches the documented pattern.

Module layout

LayerDirectoryPurposeNotes
Shared utilitiespackages/lib/Crypto, errors, logger, server helpersConstrained not to import features, trpc, or app-store
Integrationspackages/app-store/Calendar, video, payment adaptersOne directory per provider, generated type files
Domainpackages/features/Vertical slices per featureServices, repositories, DI, components per feature
APIpackages/trpc/Type-safe routersThin routers, delegate to services
Platform APIapps/api/v2/NestJS external-consumer APIControllers, pipes, guards, versioned endpoints
Legacy APIapps/api/v1/Express APIBeing phased out
Frontendapps/web/Next.js 16, App Routerapp/ pages, modules/ views, components/ shared

Files and modules with the most dependencies

Afferent coupling counts how many modules depend on this one. Efferent coupling counts how many modules this one depends on. The module friction table from Octokraft's graph analysis shows the most-connected directories:

ModuleAfferent couplingEfferent coupling
packages/app-store/_utils17228
packages/features/bookings/lib104133
apps/web/modules/bookings/components24109
packages/trpc/server/routers/viewer/organizations7185
apps/web/modules/ee/teams/components3090
apps/api/v2/src/ee/bookings/2024-08-13/services1672

Three patterns of coupling sit in this table, and each one means something different for a refactor.

Widely used utility (high afferent, low efferent). packages/app-store/_utils is imported by 172 other modules and imports 28. A shared helper being used everywhere is what a shared helper is for — this row is normal. A refactor target only if the utility grows its own dependencies.

God module (high in both directions). packages/features/bookings/lib imports 133 modules and is imported by 104. The architecture review names this row directly. High bidirectional coupling is what makes a module hard to test, hard to change, and hard to extract from — every change ripples both upstream and downstream.

Fan-out consumer (high efferent, low afferent). packages/trpc/server/routers/viewer/organizations imports from 185 modules and only 7 depend on it. The frontend counterparts — apps/web/modules/bookings/components (109 efferent), apps/web/modules/ee/teams/components (90 efferent), and apps/api/v2/src/ee/bookings/2024-08-13/services (72 efferent) — fit the same pattern. These are modules that compose many helpers to build one feature. They break first when an upstream signature changes, and the usual improvement is consolidating the helpers they pull from into a smaller set of interfaces.


Conventions

A convention, as Octokraft's convention detector defines it, is a pattern the team has chosen and repeated enough times across the codebase that it shows up as a rule. The detector does not know the rule in advance. It reads the repo, clusters similar structures by role (middleware, business logic, data access, UI, and so on), and reports the dominant pattern it sees. A convention is strong when most of its instances match. A deviation is an instance that breaks from the pattern — sometimes intentional, sometimes drift.

For cal.com, Octokraft detected 58 conventions across 7 role groups. Overall convention consistency is 92.36%, with 11 deviations across the whole project.

Role groupConventions detected
request_handlers12
business_logic11
middleware8
configuration7
utilities7
ui_components7
data_access6

The conventions below are curated from the detector's output. Each is shown with the dominant value Octokraft identified and a code fragment from the repo that matches it.

Exception filters use a consistent catch-log-respond pattern

Detected by Octokraft's convention detector. Role group: middleware. Conformance: 5/5 (100%).

All five NestJS exception filters under apps/api/v2/src/filters/ follow the same sequence: extract the HTTP context, set the request-id header with a fallback, extract user context, log a structured error, and return a standardized JSON response. HttpExceptionFilter shows the full pattern:

catch(exception: HttpException, host: ArgumentsHost) {
  const ctx = host.switchToHttp();
  const response = ctx.getResponse<Response>();
  const request = ctx.getRequest<Request>();
  const requestId = request.headers["X-Request-Id"] ?? "unknown-request-id";
  response.setHeader("X-Request-Id", requestId.toString());
  const userContext = extractUserContext(request);
  this.logger.error(`Http Exception Filter: ${exception?.message}`, { /* ... */ });
  response.status(statusCode).json({
    status: ERROR_STATUS,
    timestamp: new Date().toISOString(),
    path: request.url,
    error: { code: exception.name, message: exception.message, details: exception.getResponse() },
  });
}

The same structure appears in prisma-exception.filter.ts, trpc-exception.filter.ts, zod-exception.filter.ts, and the remaining filter. Three related conventions sit on top of it — the response-body shape, the request-id fallback string, and the per-class Logger name — all at 5/5 compliance.

Services receive repositories via constructor injection

Detected by Octokraft's convention detector. Role group: business_logic. Conformance: 3/3 (100%).

Every service in apps/api/v2/src/ee/bookings/2024-08-13/services/ takes its repositories as constructor parameters and never calls Prisma directly:

@Injectable()
export class BookingGuestsService_2024_08_13 {
  constructor(
    private readonly bookingsRepository: BookingsRepository_2024_08_13,
    private readonly bookingsService: BookingsService_2024_08_13,
    private readonly platformBookingsService: PlatformBookingsService
  ) {}
}

Two related conventions sit at 100% alongside it: services delegate cross-domain operations to shared services, and data-access goes through repository interfaces rather than direct Prisma calls.

Service classes carry a date-based API version suffix

Detected by Octokraft's convention detector. Role group: business_logic. Conformance: 3/3 (100%).

Versioned service classes in apps/api/v2/ are named with a date suffix like _2024_08_13:

export class BookingGuestsService_2024_08_13 { /* ... */ }
export class BookingLocationService_2024_08_13 { /* ... */ }
export class BookingAttendeesService_2024_08_13 { /* ... */ }

The same suffix runs through repositories (BookingsRepository_2024_08_13) and DTOs (BookingAttendeeOutput_2024_08_13). The naming lets multiple API versions coexist in the same process.

tRPC handlers use a typed options object

Detected by Octokraft's convention detector. Role group: request_handlers. Conformance: 3/3 (100%).

Handler functions in packages/trpc/server/routers/apps/routing-forms/ accept a single destructured options object rather than individual parameters:

export const formQueryHandler = async ({ ctx, input }: FormsHandlerOptions) => {
  const { prisma, user } = ctx;
  const form = await prisma.app_RoutingForms_Form.findFirst({
    where: {
      AND: [
        entityPrismaWhereClause({ userId: user.id }),
        { id: input.id },
      ],
    },
    // ...
  });
};

Two permission conventions sit on top of this at 100%: permission checks precede data operations, and database queries wrap the where clause with entityPrismaWhereClause({ userId }) to scope by ownership.

Configuration is loaded through a typed function

Detected by Octokraft's convention detector. Role group: configuration. Conformance: 1/1 (100%) + 3/3 (100%) for inline defaults + 3/3 (100%) for domain nesting.

Configuration for API v2 lives in apps/api/v2/src/config/app.ts behind a loadConfig() function rather than a module-level object. Defaults are passed inline to getEnv():

const loadConfig = (): AppConfig => {
  const env = getEnv("NODE_ENV", "development");
  const apiPort = Number(getEnv("API_PORT", "5555"));
  return {
    api: { port: apiPort, keyPrefix: getEnv("API_KEY_PREFIX", "cal_"), /* ... */ },
    db: { readUrl: getEnv("DATABASE_READ_URL", ""), /* ... */ },
    stripe: { apiKey: getEnv("STRIPE_API_KEY", ""), /* ... */ },
  };
};

Boolean environment variables are compared against the string "true" rather than using truthiness checks (usePool: getEnv("USE_POOL", "true") === "true"). The related convention at 100% is that the config type definition lives in a separate type.ts file alongside the loader.

App Router pages wrap legacy views through withAppDirSsr

Detected by Octokraft's convention detector. Role group: ui_components. Conformance: 3/4 (75%).

The team has not rewritten data fetching for every page. Instead, App Router pages under apps/web/app/(booking-page-wrapper)/ adapt the legacy getServerSideProps through a withAppDirSsr wrapper and delegate rendering to legacy view components:

const getData = withAppDirSsr<LegacyPageProps>(getServerSideProps);

export default async function ServerPage({ params, searchParams }) {
  const legacyCtx = buildLegacyCtx(await headers(), await cookies(), await params, await searchParams);
  const props = await getData(legacyCtx);
  return eventData?.interfaceLanguage
    ? <CustomI18nProvider>{<LegacyPage {...props} />}</CustomI18nProvider>
    : <LegacyPage {...props} />;
}

Four conventions in ui_components together describe the App Router migration: wrap through withAppDirSsr, build a legacy context via buildLegacyCtx, wrap in CustomI18nProvider only when locale data is present, and compute robots metadata from eventData visibility. The one deviation in this group is a page that returns LegacyPage directly without the locale check.

Deviations

Octokraft's convention detector reports 11 deviations across the project. The conventions with the weakest compliance:

ConventionRole groupComplianceWhere it drifts
Unused parameters prefixed with underscorenaming / utilities2/4 (50%)apps/api/v1/lib/helpers/ middleware inconsistent on _req vs req
Logger instantiated per class with class namestructure / business_logic3/5 (60%)Two service classes under apps/api/v2/src/ee/bookings/2024-08-13/services/ skip the private logger
Repository adapters instantiate underlying repository per method callstructure / data_access2/3 (66%)NestJS booking-references.repository.ts uses constructor-injected dbRead — intentional pattern split
Prisma client imported from shared prisma packageimports / data_access2/3 (66%)Same NestJS repository uses PrismaReadService wrapper instead of the shared @calcom/prisma import
Adapters delegate without business logicapi_design / data_access2/3 (66%)GoogleCalendarSubscription.adapter.ts contains validation and API logic — intentional, documented
Factories inject dependencies via parametersstructure / utilities2/3 (66%)BillingPortalServiceFactory.ts imports TeamRepository and prisma directly instead of receiving them
Legacy pages wrapped in CustomI18nProvider when interfaceLanguage existsstructure / ui_components2/3 (66%)apps/web/app/(booking-page-wrapper)/[user]/page.tsx returns legacy page without the locale check

The data-access deviations split between a newer NestJS-style repository pattern (PrismaReadService via constructor) and the older @calcom/prisma global import. Both forms work; they represent two generations of the same decision sitting side by side.


Issues at a glance

Octokraft records 5 critical, 16 high, 3,145 medium, 403 low, and 1 informational finding on the analyzed commit. The full breakdown by category and severity:

CategoryCriticalHighMediumLowInfoTotal
testing122,395002,398
dead_code002873200607
code_smell21443810527
security111142129
runtime117009
consistency012003

The subsections below walk each category.

Security — 29 findings

All numbers and findings in this subsection come from Octokraft's security analysis.

Octokraft's security analysis flags 29 findings across the project. One is critical: a call to createDecipheriv in packages/lib/crypto/keyring.ts:120 is missing the expected authentication-tag length argument for Galois Counter Mode. Without the length check, the application can be tricked into accepting a shorter-than-expected authentication tag, which a determined attacker can use to forge ciphertexts or recover the implicit GCM authentication key. Nine of the eleven high-severity findings cluster around React components that render HTML through dangerouslySetInnerHTML from values that are not compile-time constants.

DirectoryFindings
apps/web12
packages/app-store4
packages/emails3
packages/app-store-cli2
packages/embeds2
Dockerfile1
apps/api1
packages/features1
packages/lib1
packages/testing1

Representative findings Octokraft surfaced:

  • critical
    packages/lib/crypto/keyring.ts:120
    GCM createDecipheriv without expected authentication-tag length

    Allows ciphertext forgery if an attacker can supply a shortened tag.

  • high
    apps/web/calendso.yaml:349
    Google OAuth access token detected in checked-in configuration

    A live-looking OAuth access token sits in the example config file.

  • high
    apps/web/components/apps/installation/EventTypesStepCard.tsx:72
    dangerouslySetInnerHTML from non-constant source — XSS risk

    The team routes the value through markdownToSafeHTML and suppresses the Biome lint; the Semgrep rule still fires.

  • high
    apps/web/modules/videos/views/videos-single-view.tsx:588
    dangerouslySetInnerHTML in video view component

    Same pattern as the other dangerouslySetInnerHTML findings — sanitized at the call site, caught by static analysis.

  • high
    apps/web/pages/router/index.tsx:25
    dangerouslySetInnerHTML in routing page

    Pages-router entry point reachable from user-triggered routing flows.

  • high
    packages/ui/components/form/checkbox/Checkbox.tsx:101
    dangerouslySetInnerHTML inside a shared UI component

    Every call site of the shared Checkbox inherits the risk.

  • high
    packages/app-store-cli/src/utils/execSync.ts:10
    child_process call from a function argument

    Command-injection risk if the argument is ever user-controlled; the CLI runs locally so the practical exposure is narrow.

  • medium
    Dockerfile
    Image runs without a non-root USER directive

    Containers start as root by default.

  • medium
    apps/web and packages/embeds
    postMessage configured with a wildcard origin

    Six sites across the frontend send postMessage to "*" instead of a specific origin.

Runtime — 9 findings

All numbers and findings in this subsection come from Octokraft's runtime-risk analysis.

Octokraft's runtime-risk analysis records 9 findings. The critical one is calendar API rate limiting: external calendar APIs (Google, Office 365) are called without request queuing, exponential backoff, or proactive limit tracking — the architecture review flags this as a scalability risk for users who sync many calendars. One high finding is database connection-pool exhaustion in packages/prisma/: the Prisma configuration has no explicit pool limits or timeout handling, so a spike in concurrent requests can tie up every available connection. The other runtime findings are empty catch blocks in OAuth callback handlers and in code that writes to localStorage — errors there are swallowed silently.

DirectoryFindings
packages/app-store4
packages/lib2
package.json1
packages/features1
packages/prisma1

Representative findings:

  • critical
    packages/features/calendars/
    Calendar API rate limiting

    No queuing, backoff, or limit tracking for external calendar providers.

  • high
    packages/prisma/
    Connection pool exhaustion risk

    No explicit pool configuration, timeout, or lifetime cap.

  • medium
    OAuth callback handlers under packages/app-store/
    Empty catch blocks in oauth/callback.ts flows

    Provider errors are swallowed; callbacks proceed as if nothing happened.

  • medium
    packages/lib/ localStorage helpers
    Empty catch blocks swallow JSON-parse and storage errors

    Callers receive null without knowing why.

  • medium
    package.json
    form-data version mismatch with axios dependency

    Transitive dependency tension flagged by the supply-chain scan.

Testing — 2,398 findings

All numbers and findings in this subsection come from Octokraft's testing analysis and the test-quality LLM pass.

Octokraft's testing analysis records 2,398 findings. The overwhelming majority — 2,391 of them — are graph/untested_code: files and functions reachable from the tree but with no test. The testing score is 34.47. Structural coverage is 11.96%. Test-code-to-production-code ratio is 25.2%. The critical finding is named directly by Octokraft's test-quality pass: "Entire Confirm Handler Test Suite is Skipped" in confirm.handler.test.ts. An entire .skip block means the check runs zero assertions. Two other high-severity findings describe what is missing rather than what is broken — no concurrent-access tests for booking race conditions, and payment tests that use only mocks instead of any Stripe integration test.

DirectoryFindings
apps/web797
packages/features548
apps/api221
packages/app-store212
packages/trpc151
packages/lib133
packages/platform127
packages/emails74
packages/ui63
packages/coss-ui23

Representative findings:

  • critical
    confirm.handler.test.ts
    Entire confirm-handler test suite is skipped

    Named directly by Octokraft's test-quality pass. Zero assertions run.

  • high
    packages/features/bookings/booking.test.ts
    Missing concurrent-access tests for booking race conditions

    Existing tests cover the single-request path; no two-request slot-contention test.

  • high
    packages/features/ee/billing/payment.test.ts
    Payment tests use only mocks — no Stripe integration tests

    Mock-only suite is most likely to miss real integration drift.

  • medium
    ~2,391 locations
    graph/untested_code

    Files and functions reachable from the graph with no test coverage.

  • medium
    calendar-subscription mocks
    Potential mock-sync problem between duplicate proxy mocks

    Two proxy mocks can drift because neither is the source of truth.

  • medium
    calendar-subscription mocks
    Proxy-based mock returns values for unknown calendar types

    The fallback path masks missing test coverage for new providers.

Code smell — 527 findings

All numbers and findings in this subsection come from Octokraft's code-smell analysis and graph analysis.

Octokraft's code-smell analysis records 527 findings. 435 of them are graph/high_fan_out — single files that import from many different modules. 80 are graph/high_instability, and 8 are graph/god_class. The two critical findings are architecture-review items already listed earlier: layer boundary violations (packages/features/ importing from packages/trpc/, and packages/lib/ importing from packages/features/) and god-module coupling in packages/features/bookings/lib with its 133-module efferent coupling and 104-module afferent coupling.

DirectoryFindings
apps/web237
packages/features80
packages/trpc62
packages/emails49
packages/app-store43
apps/api34
packages/platform15
packages/embeds2
packages/testing2
packages/ui2

Representative findings:

  • critical
    packages/features/
    Features layer imports from packages/trpc

    Hierarchy violation — features should not depend on the routing layer above it.

  • critical
    packages/lib/
    lib layer imports from packages/features

    Hierarchy violation — shared utilities should not depend on domain code.

  • high
    packages/features/bookings/
    Testing difficulty due to coupling

    Bookings is a central hub — testing it in isolation requires mocking many dependencies.

  • medium
    435 locations
    graph/high_fan_out

    Module imports from many others — high efferent coupling.

  • medium
    80 locations
    graph/high_instability

    High efferent relative to afferent coupling.

  • medium
    8 locations
    graph/god_class

    Single class with many responsibilities.

  • medium
    webhook handlers
    Duplicated error-handling pattern across webhook handlers

    Each handler reimplements the same try/catch/log sequence.

Dead code — 607 findings

All numbers and findings in this subsection come from Octokraft's dead-code analysis and graph analysis.

Octokraft's dead-code analysis records 607 findings, all of the pattern graph/dead_code — exports that nothing else imports. Dead code score is 94.73, so the relative weight on the codebase is low; the absolute count reflects the 764,436-line size of the repo. Two areas concentrate it: apps/web (196 findings, mostly unused helpers and views left from the App Router migration) and packages/coss-ui (30 findings, a component set imported selectively). The packages/app-store/ integrations contribute another 43 — one-off integrations that ship both a preview and a production path.

DirectoryFindings
apps/web196
packages/features155
apps/api74
packages/app-store43
packages/lib39
packages/coss-ui30
packages/trpc16
packages/ui15
packages/embeds10
packages/platform10

Representative findings:

  • medium
    apps/web/modules/
    Unused exports from modules not yet migrated to App Router

    Legacy views still compiled but no longer imported.

  • medium
    packages/coss-ui/src/components/
    Unused shadcn component wrappers

    Component set imported selectively — the rest ships dead.

  • medium
    packages/app-store/*/
    Unused helpers and types in integration packages

    Generated scaffolds kept in place for consistency across integrations.

  • low
    packages/lib/server/
    Unused server-side helpers

    Server-only utilities that no longer have call sites.

  • low
    packages/features/*/lib/
    Unused internal utilities inside feature slices

    Per-feature helpers kept past their call-site lifetime.

Consistency — 3 findings

All numbers and findings in this subsection come from Octokraft's convention detector and architecture review.

Octokraft's convention detector records only 3 consistency findings at issue-level severity, which matches the high convention-consistency score of 92.36. The high-severity one is the architecture-review finding on static-method repositories: 65% of repositories under packages/features/*/repositories/ use static methods and import the shared Prisma client, instead of following the documented constructor-injection pattern. Two medium findings in packages/features/ and packages/trpc/ round out the list.

DirectoryFindings
packages/features2
packages/trpc1

Representative findings:

  • high
    packages/features/*/repositories/
    Static-method repositories

    65% bypass the documented constructor-injection pattern.

  • medium
    packages/features/
    Pattern drift flagged by the architecture review

    A feature slice deviates from the documented structural pattern.

  • medium
    packages/trpc/
    Pattern drift flagged by the architecture review

    A tRPC router deviates from the documented thin-router pattern.


Findings, grouped by pattern

The issue totals in the previous section list every finding. What follows is different: eight patterns that account for the large majority of the interesting ones. Each pattern describes what the team did, why it showed up, and a plain improvement direction.

1. A documented dependency hierarchy enforced by comments, not tooling

From Octokraft's architecture review — two critical arch-review/dependencies findings, one in packages/features/ and one in packages/lib/.

The architecture file at agents/rules/ documents a strict layer order: libapp-storefeaturestrpcapps/web. Lower layers are not to import from higher ones. Octokraft's architecture review flags two broken directions. packages/features/ contains files that import from packages/trpc/. packages/lib/ contains files that import from packages/features/.

The architecture documentation block also names these as documented rules:

packages/lib cannot import from packages/features, packages/trpc, packages/app-store
packages/features cannot import from packages/trpc

Why it exists. Frontend hooks that use tRPC grew inside packages/features/ alongside the domain code. Shared helpers that needed domain types crept into packages/lib/. No linter was enforcing the documented hierarchy, so the comments stayed while the imports drifted.

Improvement direction. Octokraft's architecture review recommends automated ESLint rules to block cross-layer imports, along with relocating framework-specific hooks from packages/features/ to apps/web/modules/.

2. Bookings as a central hub

From Octokraft's architecture review — a high-severity arch-review/coupling finding against packages/features/bookings/ with 108 lib imports and high bidirectional coupling.

Octokraft's module friction table shows packages/features/bookings/lib with 104 afferent and 133 efferent couplings — the most connected module in the monorepo. The architecture review describes it as a god module: "decompose bookings into focused sub-modules with clear boundaries. Target reducing lib imports from 108 to <30 through abstraction extraction."

The bookings module is this heavily connected because of what bookings have to do in the product. Creating a booking touches calendars, event types, attendees, notifications, billing, workflow reminders, and video integrations. Everything feeds bookings or reads from it. The team already split some of it into packages/features/bookings/lib/service/, which Octokraft also lists in its top-friction directories.

Improvement direction. Octokraft's architecture review recommends extracting sub-modules by booking lifecycle stage (create, cancel, reschedule, reminder) and pulling shared types into a dedicated interface package so the rest of the monorepo stops depending on bookings internals.

3. Two generations of repository patterns side by side

From Octokraft's architecture review (high-severity arch-review/patterns finding on static-method repositories) and Octokraft's convention detector (weak compliance on three data-access conventions).

Octokraft's architecture review reports that 65% of repositories in packages/features/*/repositories/ expose static methods and import a shared Prisma client directly. A typical case is DestinationCalendarRepository:

export class DestinationCalendarRepository {
  static async create(data: Prisma.DestinationCalendarCreateInput) { /* ... */ }
  static async getByUserId(userId: number) { /* ... */ }
  static async find({ where }) { /* ... */ }
}

A newer form sits next to it. BookingRepository takes a Prisma client through the constructor:

export class BookingRepository implements IBookingRepository {
  constructor(private prismaClient: PrismaClient) {}
  async getFromRescheduleUid(bookingUid: string): Promise<string | null> { /* ... */ }
}

And the NestJS side under apps/api/v2/src/ee/bookings/2024-08-13/repositories/ uses PrismaReadService / PrismaWriteService wrappers injected via constructor — 100% compliant with the convention "NestJS repositories receive PrismaReadService via constructor injection" per Octokraft's convention detector. Three different styles, chronologically ordered.

Improvement direction. Octokraft recommends converting static repositories to instance methods with DI so tests can substitute a fake Prisma client. The architecture review notes this matches the pattern the team has already documented in its own architecture file.

4. Nine React components rendering HTML from non-constant values

From Octokraft's security analysis — nine high-severity react-dangerouslysetinnerhtml findings (eight in apps/web, one in packages/ui) flagged by the Semgrep TypeScript rule set.

Octokraft's security analysis flags nine high-severity uses of dangerouslySetInnerHTML where the HTML comes from a non-constant source. Eight sit in apps/web under components/apps/installation/EventTypesStepCard.tsx, modules/apps/[slug]/slug-view.tsx, modules/event-types/components/EventTypeDescription.tsx, modules/form-builder/components/FormBuilderField.tsx, modules/team/team-view.tsx, modules/users/views/users-public-view.tsx, modules/videos/views/videos-single-view.tsx, and pages/router/index.tsx. The ninth sits in packages/ui/components/form/checkbox/Checkbox.tsx.

In EventTypesStepCard.tsx:72 the team has already added a sanitization layer and a Biome ignore comment:

// biome-ignore lint/security/noDangerouslySetInnerHtml: Content is sanitized via markdownToSafeHTML
<div
  dangerouslySetInnerHTML={{
    __html: markdownToSafeHTML(description),
  }}
/>

This is a team-TODO ↔ Octokraft-finding alignment. The team knows about the risk; they route every call through markdownToSafeHTML; they documented that decision in a Biome suppression comment. Octokraft's Semgrep rule still catches the pattern because static analysis cannot verify what the sanitizer does.

Improvement direction. Two paths fit the team's existing approach. Either bundle the dangerouslySetInnerHTML call plus the markdownToSafeHTML into a dedicated component (one allowed site, every call site safe by construction), or swap in a library like DOMPurify that static-analysis rules already recognize as a safe consumer.

5. Critical crypto call missing an authentication-tag length

From Octokraft's security analysis — a single critical gcm-no-tag-length finding flagged by Semgrep against packages/lib/crypto/keyring.ts:120. This is the headline finding of the post.

keyring.ts is the module that encrypts and decrypts secrets stored in the database — API keys for calendar providers, OAuth refresh tokens, webhook signing keys. It is the file that every integration credential passes through on the way in and out. The encryption side of the module produces a versioned envelope:

return {
  v: 1,
  alg: "AES-256-GCM",
  ring,
  kid,
  nonce: b64url(nonce),
  ct: b64url(ct),
  tag: b64url(tag),
};

The decryption side unpacks the envelope and calls into the Node crypto API. This is the part Octokraft's rule fires on:

const key = getKeyMaterial(envelope.ring, envelope.kid);
const nonce = unb64url(envelope.nonce);
const ct = unb64url(envelope.ct);
const tag = unb64url(envelope.tag);
const aadBuf = aadToBuffer(aad);

const decipher = createDecipheriv("aes-256-gcm", key, nonce);
decipher.setAAD(aadBuf);
decipher.setAuthTag(tag);

setAuthTag(tag) passes the tag bytes but does not declare the expected tag length. Node's crypto module accepts any tag length from 4 to 16 bytes. A stored envelope whose tag field has been trimmed to 4 bytes still decrypts successfully. The shorter the tag, the smaller the space of forgeries an attacker has to search through. With a 4-byte tag and enough oracle attempts, forging ciphertexts or recovering the GCM authentication key becomes tractable — Octokraft's description: "can be abused by an attacker to spoof ciphertexts or recover the implicit authentication key of GCM, allowing arbitrary forgeries."

The realistic threat model depends on whether an attacker can ever write into the envelope field directly. If the envelope always comes from the same database the service controls, forging a tag requires first compromising the database. If any path writes envelope bytes from untrusted input — a webhook, an import path, a multi-tenant boundary — the short-tag attack applies.

Why it exists. The Node crypto API does not require the tag length argument. Code that has always worked stays. The Semgrep rule exists because the API accepts a shorter tag by default.

Improvement direction. Two lines of defense, both one-liners. Add if (tag.length !== 16) throw new Error("Invalid auth tag length"); before createDecipheriv. Or pass the length explicitly: decipher.setAuthTag(tag, { authTagLength: 16 });. Either change closes the finding.

6. Rate-limiting and connection-pool headroom not yet in place

From Octokraft's architecture review and runtime-risk analysis — one critical arch-review/scalability on calendar rate limiting, one high arch-review/scalability on Prisma connection pooling.

Octokraft's architecture review marks two scalability risks as acute. The first is calendar API rate limiting. Google Calendar and Microsoft Office 365 both enforce per-user and per-project quotas. The code under packages/features/calendars/ and packages/app-store/*calendar*/ calls these APIs without a queue, without exponential backoff, and without a token-bucket to track remaining quota. A user with many calendars or a burst of imports can exhaust quota for the whole deployment.

The second is the Prisma connection pool. The Prisma configuration in packages/prisma/ does not set explicit pool limits, wait timeouts, or connection-lifetime caps. Under concurrency the pool can fill with stalled connections.

Improvement direction. For the calendar APIs, Octokraft recommends a request queue with exponential backoff and proactive quota tracking per integration. For Prisma, explicit pool size, query timeout, and pool-exhaustion metrics feeding an alert.

7. Tests that describe more than they check

From Octokraft's testing analysis — 2,398 findings, dominated by graph/untested_code (2,391) with three team-authored signals named explicitly by the test-quality pass.

The raw testing count is dominated by graph/untested_code: reachable files and functions with no coverage. The structural coverage number is 11.96%. The three findings that describe the team's own test code are more telling than the count:

  • Entire confirm-handler test suite is skipped. The file confirm.handler.test.ts contains a .skip block that disables every assertion. Octokraft's test-quality pass flagged this as critical.
  • Missing concurrent-access tests for booking race conditions. The existing booking tests cover the single-request path; they do not exercise two requests touching the same slot.
  • Payment tests use only mocks — no Stripe integration tests. The payment test suite runs entirely against mocked Stripe calls.

These are signals the team would recognize. A skipped test is a decision someone made and never came back to. Missing concurrent-access tests is a listed gap in a product that books finite time slots. Mock-only payment tests are the ones most likely to miss real integration drift.

Improvement direction. Walk the three named findings first — re-enable or delete the skipped block, add a concurrent-access test for the slot-contention path, and add at least one end-to-end Stripe test against a test-mode account. The 2,391 untested-code findings are the long tail; the three named ones are the priorities Octokraft identified.

8. Empty catch blocks in OAuth and storage code paths

From Octokraft's runtime-risk analysis — three related llm_health/runtime/empty_catch_block_* findings across OAuth callback handlers and localStorage helpers.

Octokraft's runtime-risk analysis records empty catch blocks in three places: OAuth callback handlers under packages/app-store/, JSON parse paths, and localStorage helper functions in packages/lib/. Each one catches an error and discards it. When OAuth providers return an error (revoked grant, rate limit, malformed response) the callback proceeds as if nothing happened; when localStorage.getItem throws (private mode, quota exceeded) the caller gets null without knowing why.

Why it exists. OAuth callbacks run in paths where a thrown error corrupts the UI. localStorage calls throw in browser contexts where the storage API is restricted. A silent catch was the fastest way to keep the main path alive.

Improvement direction. Log and rethrow for OAuth callbacks — silent drops mean no signal when grants start failing. For localStorage, a single helper that wraps the call, logs once, and returns a typed Result so each caller decides what the absence of data means.


Tradeoffs visible in the code

The previous section lists patterns Octokraft detected. This section lists tradeoffs the team itself has called out — TODO comments, FIXME markers, @deprecated annotations — and the Octokraft findings they line up with. These alignments are the most useful signal in the codebase: someone on the team already knew, wrote it down, and has not got back to it yet.

Repositories still on direct-Prisma imports — with followup PR notes

This is the standout alignment for this section. Octokraft's architecture review flagged a repository-DI problem. The team has been writing the fix, in public, in the code itself.

In packages/features/bookings/lib/handleCancelBooking.ts:50 the team left the intent explicit:

// TODO: Prisma import would be used from DI in a followup PR when we remove `handler` export

Two more of the same kind sit nearby. packages/features/bookings/lib/service/RegularBookingService.ts:547:

// TODO: Moving it to instance based access through DI in a followup

And packages/features/bookings/lib/service/InstantBookingCreateService.ts:183:

// TODO: In a followup PR, we aim to remove prisma dependency and instead inject the repositories as dependencies.

The same intent appears in at least four other places around the bookings and managed-events code: managedEventReassignment.ts:10 and managedEventManualReassignment.ts:12 both carry // TODO: Remove this function with better dependency injection, and WebhookRepository.ts:58 deprecates a method with @deprecated Use DI container instead:. The migration is partial, documented, and visible.

Aligned Octokraft finding. Octokraft's architecture review puts the same finding in structural terms: 65% of repositories under packages/features/*/repositories/ use static methods and import a shared Prisma client, instead of the constructor-injection pattern the team has already documented as the target. On the NestJS side of the monorepo (apps/api/v2/), the target pattern holds at 100% compliance — the new code is proof-of-concept for what the TODOs describe.

The team has already planned the migration. The blocker is scale — 65% of repositories means many call sites to move. Each migration is a non-trivial PR because the call site has to switch from Repository.staticMethod(args) to receiving the repo through the constructor. The "followup PR" in every TODO is the same followup PR, still in progress.

Webhook repository marked deprecated in favor of DI

In packages/features/webhooks/lib/repository/WebhookRepository.ts:58:

/**
 * @deprecated Use DI container instead:
 */

Aligned Octokraft finding. Same repository-pattern migration as above. The team has marked one way as deprecated and documented the new path, but the deprecated methods still exist because call sites have not been moved over. Octokraft's convention detector shows three data-access conventions at 66% compliance as a consequence.

Managed-event reassignment functions flagged for removal

In packages/features/ee/managed-event-types/reassignment/managedEventReassignment.ts:10 and a sibling file managedEventManualReassignment.ts:12:

// TODO: Remove this function with better dependency injection

Aligned Octokraft finding. Again the same migration. This pair of TODOs is specific — the team names the replacement approach and tags the old function as the exact piece to delete once the DI-based version ships.

Untyped fields in the booking repository

In packages/features/bookings/repositories/BookingRepository.ts:1149-1151:

// FIXME: metadata is untyped
// FIXME: responses is untyped

Aligned Octokraft finding. The architecture review lists Bookings as the god module with 108 lib imports. Part of why it has this much coupling is that the repository returns data shapes whose internals are not fully typed — callers build their own assumptions. Tightening these FIXMEs would reduce some of what the architecture review recommends extracting into typed sub-modules.

Rate limiting for AI phone calls — team-authored TODO

In packages/features/tasker/tasks/executeAIPhoneCall.ts:197:

// TODO: add better rate limiting for AI phone calls

Aligned Octokraft finding. Octokraft's architecture review lists calendar API rate limiting as a critical scalability finding. The same class of issue (external API quotas, no queue, no backoff) applies to the AI phone-call code path; the team has already flagged it.

Workflow service migration explicitly called out

In packages/features/ee/workflows/lib/service/WorkflowService.ts:21:

// TODO (Sean): Move most of the logic migrated in 16861 to this service

This is a named, assigned TODO tied to a PR number. It is the kind of marker that only stays in the code if the migration has stretched across multiple releases. The service layer in workflows is partially in place; the rest of the logic sits in older lib/ files.

Webhook service defers env-variable injection for testability

In packages/features/webhooks/lib/service/WebhookService.ts:27:

// TODO: Ideally we should inject this flag as well. Would be awesome when we would be able to unit test without mocking env variables too. Also with trigger.dev, this would be further worked on, so we can leave this as is for now

Aligned Octokraft finding. The testing score is 34.47 — untested code dominates the category. This TODO names two of the drivers: env-variable mocking and the async Trigger.dev path. The team has already decided to wait for the Trigger.dev work before changing the test setup.

Legacy Sendgrid workflow provider marked deprecated

In packages/features/ee/workflows/lib/reminders/providers/sendgridProvider.ts:

/**
 * @deprecated use smtp with tasker instead
 */

And in the related emailReminderManager.ts:264 and api/scheduleEmailReminders.ts:2, the same note appears. A whole provider path is marked deprecated but still exported and used.

Aligned Octokraft finding. Octokraft's dead-code analysis counts 607 findings. Many of them are entries reachable from the graph that are flagged deprecated but still wired in. The trade the team has made is clear: route new traffic through the replacement while the old path stays to serve in-flight jobs.

Email requeue on failure in the cancel-booking path

In packages/features/bookings/lib/handleCancelBooking.ts:722, inside the booking-cancellation flow:

// TODO: if emails fail try to requeue them

When a booking is cancelled the handler sends notification emails to attendees and organizers. If the email call raises an error, the code logs the problem and moves on — the attendee never hears that their booking was cancelled. The TODO names the fix: route failed sends onto the Trigger.dev queue so they retry with backoff.

Aligned Octokraft finding. Octokraft's runtime-risk analysis records empty catch blocks across OAuth callbacks and localStorage helpers. This is the same class of silent-drop — a user-facing side effect that fails quietly under load.

API v1 deprecation markers still in booking service

In packages/features/bookings/lib/service/RegularBookingService.ts:3022:

/**
 * @deprecated Exists only till API v1 is removed.
 */

Aligned architecture. The monorepo has both apps/api/v1/ (Express, documented as being phased out) and apps/api/v2/ (NestJS) running in parallel. The deprecated method is one of the bridges. The team's own architecture file notes v1 is being phased out; this marker tells a reader which specific symbols are waiting for that date.


What cal.com does well

The scorecard in the architecture section captures the low points. This section captures the parts that carried high marks in Octokraft's output.

  • NestJS exception filters run with real discipline. Octokraft's convention detector reports 100% compliance across five exception filters under apps/api/v2/src/filters/ for every related convention — catch-log-respond sequence, response-body shape, request-id fallback, per-class Logger name, helper-function usage for user context and header filtering. The five filters (HttpExceptionFilter, PrismaExceptionFilter, TRPCExceptionFilter, ZodExceptionFilter, and the remaining filter in the same directory) each extract the HTTP context, set X-Request-Id with an "unknown-request-id" fallback, call extractUserContext(request) and filterReqHeaders(request.headers), log a structured error with the same fields in the same order, and respond with { status, timestamp, path, error: { code, message } } — five filters, one pattern, nothing drifting across any of the six related conventions the detector tracked.
  • Dependency injection is type-safe and build-checked. Octokraft's architecture review calls the @evyweb/ioctopus setup under packages/features/*/di/ a structural strength. Missing dependencies fail at build time via the type system, not at runtime.
  • Vertical slices keep domain code together. Octokraft's architecture review lists this as the first of three structural strengths: each feature under packages/features/ has services/, repositories/, components/, lib/, di/, and tests in one directory.
  • The repository pattern is documented and enforced at the service layer. Octokraft's architecture review credits the team with "well-documented repository pattern isolating Prisma ORM from business logic." Across the NestJS service layer, every service receives its repositories through the constructor — Octokraft's convention detector reports 100% compliance on that pattern. Services depend on interface contracts, which keeps the Prisma dependency bounded to the repository implementations.
  • Convention consistency is 92.36%. Across 58 detected conventions, Octokraft's convention detector reports only 11 deviations. The codebase is internally consistent.
  • Duplication score is 100. Octokraft's duplication analysis finds no significant copy-paste code across the 764,436 lines. Shared helpers are extracted into packages/lib/ and packages/ui/.
  • Dead code score is 94.73. Octokraft's dead-code pass counts 607 findings, but the relative score is near the top of the category. The codebase is large and the unreachable share is small.
  • Compliance score is 100. Octokraft's compliance pass finds no license, header, or copyright issues.
  • Service classes carry explicit version suffixes. The _2024_08_13 suffix across service classes, repositories, and DTOs is a team-wide convention Octokraft's detector finds at 100%. Multiple API versions run in the same process without name collisions.
  • tRPC handlers consistently follow a typed options-object pattern. Octokraft's convention detector finds 100% compliance on { ctx, input } destructuring across the routing-forms handlers. Two related permission conventions sit at 100% on top of it — permission checks precede data operations, and queries wrap with entityPrismaWhereClause to scope by ownership.
  • Exceptions are structured, not opaque. Octokraft's convention detector finds 100% compliance on guards throwing specific BadRequestException / ForbiddenException / UnauthorizedException types rather than returning false. Error responses carry { status, timestamp, path, error: { code, message } } every time.
  • Async workflow code uses a real job system. Trigger.dev handles reminders, notifications, and calendar syncs with retry logic and concurrency caps. The booking-notifications queue caps concurrency at 20 in packages/features/bookings/lib/tasker/trigger/notifications/config.ts.
  • Configuration is typed and tested for booleans. API v2 config uses getEnv("USE_POOL", "true") === "true" rather than truthiness — Octokraft's convention detector reports 100% compliance on this pattern. Config types live in a dedicated type.ts file alongside the loader.
  • The test stack is current. Vitest 4.0.16 for unit, Playwright 1.57.0 for end-to-end. The team has 192,653 lines of test code against 764,436 lines of production code — roughly a 1:4 ratio.

Closing summary

Cal.com is a large TypeScript monorepo in transition. The newer NestJS API v2 runs with strong discipline: Octokraft's convention detector finds 100% compliance across exception filters, guards, service DI, and configuration. The older Next.js frontend and the shared features and lib layers carry the debt Octokraft's architecture review calls out: layer boundary violations, a god-module pattern in bookings, and 65% of repositories still using static methods. The two sides coexist in one repo, and the team has left TODOs and @deprecated markers across the older code that line up exactly with Octokraft's findings — DI migrations, untyped fields, rate-limiting gaps, and a deprecated workflow provider.

The security number is the one to watch. Octokraft records one critical finding — a GCM createDecipheriv call missing the expected authentication-tag length in packages/lib/crypto/keyring.ts:120 — and eleven high-severity findings, mostly dangerouslySetInnerHTML in React components where the team has already added sanitization via markdownToSafeHTML. The latter is the pattern worth emphasizing: Octokraft caught it, the team already knew, and the Biome suppression comment proves it. The gap between what a team knows and what external analysis can verify is narrow here, but the two critical structural numbers — a C-rated architecture and a 53.62 security score — still matter on a codebase this size.


Explore in the showcase

Octokraft's public showcase for cal.com is available at https://app.octokraft.com/showcase/CALCOM. From there you can:

  • Browse the full list of 5 critical, 16 high, and 3,145 medium findings, filtered by category and severity.
  • Open the architecture review in detail — module layout, coupling graph, strengths, weaknesses, and per-dimension scores.
  • Read every detected convention with its code examples, compliance rate, and deviation cases.
  • Drill into the bookings god-module and its 108 lib imports.
  • Cross-reference the dangerouslySetInnerHTML findings with the sanitization helper the team uses at every call site.

Methodology

Octokraft analyzed the calcom/cal.com repository at commit d08f4a02156bcea308a97ea8ccde919f56340520 on the main branch. The pipeline ran the same five passes it runs on every project:

  1. Static analysis and graph extraction. Semgrep rules for security findings, graph analysis for coupling, fan-out, instability, dead code, and god-class detection.
  2. Convention detection. LLM-assisted clustering across files grouped by role (middleware, business logic, data access, UI, configuration, utilities, error handling, naming, imports). Each convention is reported with its dominant value, compliance rate, and examples.
  3. Architecture review. Cross-cutting structural review across modularity, coupling, scalability, and patterns, producing strengths, weaknesses, and recommendations.
  4. Test-quality pass. LLM-assisted review of the test suite that surfaces skipped blocks, missing categories (concurrency, integration), and mocking patterns.
  5. Runtime-risk pass. Targeted review of error-handling paths (empty catch blocks, swallowed promise rejections), external-API call patterns, and database-connection handling.

Every number in this post traces back to Octokraft's output. Every file path resolves in the repository at the analyzed commit.

Run the same analysis on your codebase

Security, runtime risks, test quality, structural health, convention drift, dead code -- the same eight dimensions, the same standards. See where your code stands.

Try Octokraft