Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@BilalG1
Copy link
Contributor

@BilalG1 BilalG1 commented Nov 26, 2025

Summary by CodeRabbit

  • New Features

    • ClickHouse analytics backend: store/query events, admin analytics query API (includes CPU and wall-clock timings) and migration/backfill endpoints.
  • Dashboard

    • Interactive ClickHouse SQL editor and a migration UI with progress, controls, and batch management.
  • Infrastructure

    • Local/CI ClickHouse service, environment configuration, and run scripts integrated into compose/CI.
  • Tests

    • New end-to-end tests for analytics queries/events; snapshot redactions for timing values.
  • Docs / Errors

    • New user-facing analytics error types for timeouts and query failures.

✏️ Tip: You can customize this high-level summary in your review settings.


Note

Add ClickHouse-backed analytics: readonly query API with timing, dual-write event storage and migration, dashboard query editor/migration UI, and local/CI setup.

  • Backend (Analytics + ClickHouse):
    • ClickHouse integration: New @/lib/clickhouse client, env vars, and migrations creating analytics.events; row policy and restricted limited_user grants.
    • Event ingestion: Dual-write backend events to analytics.events (src/lib/events.tsx).
    • Query API: New admin endpoint POST /api/v1/internal/analytics/query executing readonly, tenant-scoped ClickHouse queries and returning results + CPU/wall times; adds KnownErrors for analytics.
    • Migration API: Internal endpoint to backfill events from Postgres to ClickHouse with cursor/limits; wired into DB migration script.
  • Dashboard:
    • Analytics Query UI: Page with ClickHouse SQL editor (Monaco, autocomplete) and JSON results.
    • ClickHouse Migration UI: Internal page to run batched backfills with progress/cursor.
  • Shared/SDK:
    • Admin interface additions (queryAnalytics, ClickHouse migration) and analytics types.
  • DevOps:
    • Docker Compose/CI add ClickHouse services; .env examples and clickhouse scripts; Dev Launchpad entries.
  • Tests:
    • E2E coverage for analytics query behavior, permissions/limits, event visibility; snapshot serializer redacts timing fields.

Written by Cursor Bugbot for commit 1134883. This will update automatically on new commits. Configure here.

@vercel
Copy link

vercel bot commented Nov 26, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
stack-backend Ready Ready Preview, Comment Jan 28, 2026 5:16pm
stack-dashboard Ready Ready Preview, Comment Jan 28, 2026 5:16pm
stack-demo Ready Ready Preview, Comment Jan 28, 2026 5:16pm
stack-docs Ready Ready Preview, Comment Jan 28, 2026 5:16pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 26, 2025

📝 Walkthrough

Walkthrough

Adds ClickHouse integration: infra and CI updates, client library and utilities, migrations, dual-write event logging, admin analytics query and backfill APIs, dashboard query/migration UIs, types/interfaces, E2E tests, and two analytics-specific KnownErrors.

Changes

Cohort / File(s) Summary
Infra & Env
apps/backend/.env, apps/backend/.env.development, docker/server/.env.example, docker/dependencies/docker.compose.yaml, .github/workflows/docker-server-build-run.yaml, apps/dev-launchpad/public/index.html, package.json, apps/backend/package.json
Add ClickHouse env vars, Docker Compose service & volume, CI step to start ClickHouse, dev-launchpad UI entries, and root/backend clickhouse run scripts.
Backend scripts & migrations
apps/backend/scripts/clickhouse-migrations.ts, apps/backend/scripts/db-migrations.ts
New ClickHouse migration script (DB/table/view/user/policies) and integration into existing DB migrate/drop flows.
ClickHouse client & utils
apps/backend/src/lib/clickhouse.tsx
New client factory, admin/external clients, config flag, and getQueryTimingStats (flush logs + read system.query_log).
Event dual-write & tokens
apps/backend/src/lib/events.tsx, apps/backend/src/lib/tokens.tsx
Conditional ClickHouse dual-write of events and inclusion of teamId in event logging payload.
Analytics query API
apps/backend/src/app/api/latest/internal/analytics/query/route.ts
New admin-only POST route executing scoped ClickHouse queries with params, timeouts, limits, timing stats and error mapping.
ClickHouse migrations API
apps/backend/src/app/api/latest/internal/clickhouse/migrate-events/route.tsx
New internal POST route to backfill Postgres events into ClickHouse with cursor paging, idempotency, progress and stats.
Dashboard — Query UI
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/query-analytics/*, apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/query-analytics/page.tsx
New Monaco-based ClickHouse SQL editor, language definition, autocomplete, run shortcut, adminApp.queryAnalytics integration and results pane.
Dashboard — Migration UI
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/clickhouse-migration/*, apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/clickhouse-migration/page.tsx
New migration UI/page to run batched migrations with start/stop/reset, cursor handling, progress and stats.
Types & Admin interface
packages/stack-shared/src/interface/crud/analytics.ts, packages/stack-shared/src/interface/admin-interface.ts, packages/template/src/lib/stack-app/apps/interfaces/admin-app.ts, packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts
Add AnalyticsQuery* and ClickhouseMigration* types and admin methods queryAnalytics and migrateEventsToClickhouse with pass-through impl.
KnownErrors & tests infra
packages/stack-shared/src/known-errors.tsx, apps/e2e/tests/snapshot-serializer.ts, apps/e2e/tests/backend/backend-helpers.ts
Add AnalyticsQueryTimeout and AnalyticsQueryError; snapshot redaction for timing and stricter camelCase regex for tests.
E2E tests
apps/e2e/tests/backend/endpoints/api/v1/analytics-query.test.ts, apps/e2e/tests/backend/endpoints/api/v1/analytics-events.test.ts, apps/e2e/tests/*
New comprehensive tests for analytics query endpoint and ClickHouse-backed event storage/access controls, with retry and snapshot helpers.
Scripts & package metadata
apps/backend/package.json, package.json
Add @clickhouse/client dependency and clickhouse run scripts (backend + root).

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Dashboard as "Dashboard (React)"
    participant Backend as "Backend API"
    participant ClickHouse as "ClickHouse"
    participant QueryLog as "system.query_log"

    User->>Dashboard: Run SQL query (query, params, timeout_ms)
    Dashboard->>Backend: POST /internal/analytics/query
    Backend->>Backend: Authenticate (admin), generate query_id
    Backend->>ClickHouse: Execute parameterized query (scoped settings, limits)
    ClickHouse-->>Backend: JSON result rows
    Backend->>ClickHouse: SYSTEM FLUSH LOGS
    Backend->>QueryLog: SELECT cpu_time_ms, wall_clock_time_ms WHERE query_id=...
    QueryLog-->>Backend: timing stats
    Backend-->>Dashboard: { result, timing }
    Dashboard->>User: Display results + timing
Loading
sequenceDiagram
    actor User
    participant Dashboard as "Migration UI"
    participant Backend as "Backend API"
    participant Postgres as "Postgres"
    participant ClickHouse as "ClickHouse"

    User->>Dashboard: Start migration (min,max,cursor,limit)
    loop per batch
        Dashboard->>Backend: POST /internal/clickhouse/migrate-events
        Backend->>Postgres: SELECT events WHERE created_at BETWEEN range ORDER BY created_at,id LIMIT N
        Postgres-->>Backend: rows
        Backend->>Backend: Transform rows → ClickHouse JSONEachRow
        Backend->>ClickHouse: INSERT INTO analytics_internal.events (JSONEachRow, async_insert)
        ClickHouse-->>Backend: insert ack
        Backend-->>Dashboard: { processed_events, inserted_rows, progress, next_cursor }
        Dashboard->>User: Update progress
    end
    Dashboard->>User: Migration complete
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested reviewers

  • N2D4

Poem

🐇
I hopped to the logs with whiskers bright,
ClickHouse sipped events through the night.
Rows in JSON hopped in tidy rows,
Dashboards blinked as insight grows.
Nibble, count, report — analytics glows!

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'clickhouse setup' is vague and generic, failing to clearly convey the main scope of this comprehensive analytics implementation across multiple systems. Consider a more descriptive title such as 'Add ClickHouse-backed analytics with query API, event ingestion, and dashboard UI' to better communicate the breadth and purpose of changes.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The PR description is well-documented with a detailed Cursor summary covering backend analytics, dashboard, shared/SDK, DevOps, and tests components, despite using the minimal template.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Nov 26, 2025

Greptile Overview

Greptile Summary

This PR establishes the foundational ClickHouse integration for analytics, adding database infrastructure, client library, API endpoint, and comprehensive security controls.

Major Changes:

  • Added ClickHouse service to Docker Compose with proper volume management and port configuration
  • Implemented two-tier authentication system: admin user for migrations/system operations and limited_user for query execution with restricted permissions
  • Created /api/latest/analytics/query endpoint with SmartRouteHandler that enforces server-level auth, parameterized queries, configurable timeouts, and tenancy isolation
  • Added migration script that creates limited_user with SELECT-only permissions (references non-existent allowed_table1)
  • Implemented query timing stats retrieval using admin privilege escalation to access system.query_log
  • Added comprehensive E2E tests covering security boundaries (no CREATE, INSERT, or system table access)

Issues Found:

  • Migration grants SELECT on non-existent analytics.allowed_table1, which will cause migration failure
  • Missing fallback handling in getQueryTimingStats when query doesn't appear in query_log (will throw runtime error accessing undefined)

Confidence Score: 2/5

  • This PR has critical issues that will cause migration failures in production
  • Score reflects two critical bugs: the migration references a non-existent table which will fail on execution, and missing error handling in stats retrieval that will cause runtime crashes. The security model and API design are solid, but the implementation has blocking issues.
  • Pay close attention to apps/backend/scripts/clickhouse-migrations.ts (references non-existent table) and apps/backend/src/lib/clickhouse.tsx (missing error handling)

Important Files Changed

File Analysis

Filename Score Overview
apps/backend/scripts/clickhouse-migrations.ts 2/5 Added ClickHouse migration script with user creation and permissions, but references non-existent table
apps/backend/src/lib/clickhouse.tsx 3/5 Implemented ClickHouse client factory and query timing stats retrieval with admin escalation
apps/backend/src/app/api/latest/analytics/query/route.tsx 4/5 Added analytics query endpoint with proper auth, timeout handling, and error management

Sequence Diagram

sequenceDiagram
    participant Client
    participant AnalyticsAPI
    participant ClickHouse
    participant AdminConnection

    Client->>AnalyticsAPI: POST analytics query
    AnalyticsAPI->>ClickHouse: Execute with limited user
    
    alt Success
        ClickHouse-->>AnalyticsAPI: Result data
        AnalyticsAPI->>AdminConnection: Get timing stats
        AdminConnection-->>AnalyticsAPI: Stats
        AnalyticsAPI-->>Client: Result with stats
    else Timeout or Error
        ClickHouse-->>AnalyticsAPI: Error
        AnalyticsAPI-->>Client: Error response
    end
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

11 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (3)
apps/backend/scripts/clickhouse-migrations.ts (1)

9-12: Using plaintext password authentication.

While plaintext_password is acceptable for development environments, consider documenting this choice or using sha256_password for better security practices.

Example with SHA256:

import { createHash } from 'crypto';

const passwordHash = createHash('sha256')
  .update(clickhouseExternalPassword)
  .digest('hex');

await client.exec({
  query: "CREATE USER IF NOT EXISTS limited_user IDENTIFIED WITH sha256_password BY {passwordHash:String}",
  query_params: { passwordHash },
});
apps/e2e/tests/backend/endpoints/api/v1/analytics-query.test.ts (2)

1-29: Good test coverage for basic query execution.

The tests appropriately use inline snapshots as per coding guidelines. Consider adding a negative test case verifying that client accessType is rejected, since the route requires serverOrHigherAuthTypeSchema.


274-387: Security tests are comprehensive.

Good coverage of dangerous operations (CREATE TABLE, system tables, KILL QUERY, INSERT). Note that error messages expose the internal username limited_user - consider whether this level of detail should be sanitized in the route handler before returning to clients.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2cb8806 and 0c08896.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (11)
  • apps/backend/.env.development (1 hunks)
  • apps/backend/package.json (1 hunks)
  • apps/backend/scripts/clickhouse-migrations.ts (1 hunks)
  • apps/backend/scripts/db-migrations.ts (2 hunks)
  • apps/backend/src/app/api/latest/analytics/query/route.tsx (1 hunks)
  • apps/backend/src/lib/clickhouse.tsx (1 hunks)
  • apps/dev-launchpad/public/index.html (2 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/analytics-query.test.ts (1 hunks)
  • apps/e2e/tests/snapshot-serializer.ts (1 hunks)
  • docker/dependencies/docker.compose.yaml (2 hunks)
  • packages/stack-shared/src/known-errors.tsx (2 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Ensure code passes pnpm typecheck for TypeScript type validation
Never use toast for blocking alerts and errors; use alerts instead as they are more visible to users
Keep hover/click transitions snappy and fast; apply transitions after the action (e.g., smooth fade-out on hover end) rather than delaying actions with pre-transitions to avoid sluggish UI
Never use try-catch-all, never void a promise, never use .catch(console.error) or similar; use runAsynchronously or runAsynchronouslyWithAlert for error handling instead of try-catch blocks
Use loading indicators instead of try-catch blocks for asynchronous UI operations; button components like support async callbacks with built-in loading state
Use ES6 maps instead of records wherever possible

Files:

  • apps/backend/scripts/clickhouse-migrations.ts
  • apps/e2e/tests/backend/endpoints/api/v1/analytics-query.test.ts
  • apps/e2e/tests/snapshot-serializer.ts
  • apps/backend/src/app/api/latest/analytics/query/route.tsx
  • apps/backend/src/lib/clickhouse.tsx
  • packages/stack-shared/src/known-errors.tsx
  • apps/backend/scripts/db-migrations.ts
apps/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer client components and dynamic functions like usePathname over Next.js dynamic functions to keep pages static; avoid using await params or similar dynamic patterns

Files:

  • apps/backend/scripts/clickhouse-migrations.ts
  • apps/e2e/tests/backend/endpoints/api/v1/analytics-query.test.ts
  • apps/e2e/tests/snapshot-serializer.ts
  • apps/backend/src/app/api/latest/analytics/query/route.tsx
  • apps/backend/src/lib/clickhouse.tsx
  • apps/backend/scripts/db-migrations.ts
apps/e2e/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Always add new E2E tests when you change the API or SDK interface

Files:

  • apps/e2e/tests/backend/endpoints/api/v1/analytics-query.test.ts
  • apps/e2e/tests/snapshot-serializer.ts
**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{test,spec}.{ts,tsx}: Run all tests using pnpm test run; run specific tests using pnpm test run <file-filters> with Vitest
Use .toMatchInlineSnapshot for test assertions instead of other selectors when possible; check snapshot-serializer.ts for formatting and non-deterministic value handling

Files:

  • apps/e2e/tests/backend/endpoints/api/v1/analytics-query.test.ts
apps/backend/src/app/api/latest/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

In the Stack Auth monorepo backend API, organize routes by resource type (auth, user management, team management, OAuth providers) in /apps/backend/src/app/api/latest/*

Files:

  • apps/backend/src/app/api/latest/analytics/query/route.tsx
🧠 Learnings (4)
📚 Learning: 2025-11-25T02:09:03.104Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.104Z
Learning: Applies to packages/stack-shared/src/config/** : When making backwards-incompatible changes to the config schema, update the migration functions in `packages/stack-shared/src/config/schema.ts`

Applied to files:

  • apps/backend/scripts/clickhouse-migrations.ts
  • apps/backend/scripts/db-migrations.ts
📚 Learning: 2025-11-25T02:09:03.104Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.104Z
Learning: Applies to apps/e2e/**/*.{ts,tsx} : Always add new E2E tests when you change the API or SDK interface

Applied to files:

  • apps/e2e/tests/backend/endpoints/api/v1/analytics-query.test.ts
📚 Learning: 2025-11-25T02:09:03.104Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.104Z
Learning: Applies to **/*.{test,spec}.{ts,tsx} : Use `.toMatchInlineSnapshot` for test assertions instead of other selectors when possible; check snapshot-serializer.ts for formatting and non-deterministic value handling

Applied to files:

  • apps/e2e/tests/snapshot-serializer.ts
📚 Learning: 2025-11-25T02:09:03.104Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.104Z
Learning: Applies to apps/backend/src/app/api/latest/**/*.{ts,tsx} : In the Stack Auth monorepo backend API, organize routes by resource type (auth, user management, team management, OAuth providers) in `/apps/backend/src/app/api/latest/*`

Applied to files:

  • apps/backend/src/app/api/latest/analytics/query/route.tsx
🧬 Code graph analysis (3)
apps/backend/scripts/clickhouse-migrations.ts (2)
apps/backend/src/lib/clickhouse.tsx (1)
  • createClickhouseClient (11-19)
packages/stack-shared/src/utils/env.tsx (1)
  • getEnvVariable (16-58)
packages/stack-shared/src/known-errors.tsx (2)
packages/stack-shared/src/index.ts (1)
  • KnownError (14-14)
packages/stack-shared/src/utils/results.tsx (1)
  • error (36-41)
apps/backend/scripts/db-migrations.ts (1)
apps/backend/scripts/clickhouse-migrations.ts (1)
  • runClickhouseMigrations (4-24)
🪛 dotenv-linter (4.0.0)
apps/backend/.env.development

[warning] 75-75: [SubstitutionKey] The CLICKHOUSE_URL key is not assigned properly

(SubstitutionKey)


[warning] 76-76: [UnorderedKey] The CLICKHOUSE_ADMIN_USER key should go before the CLICKHOUSE_URL key

(UnorderedKey)


[warning] 77-77: [UnorderedKey] The CLICKHOUSE_ADMIN_PASSWORD key should go before the CLICKHOUSE_ADMIN_USER key

(UnorderedKey)


[warning] 78-78: [UnorderedKey] The CLICKHOUSE_EXTERNAL_USER key should go before the CLICKHOUSE_URL key

(UnorderedKey)


[warning] 79-79: [UnorderedKey] The CLICKHOUSE_EXTERNAL_PASSWORD key should go before the CLICKHOUSE_EXTERNAL_USER key

(UnorderedKey)


[warning] 80-80: [UnorderedKey] The CLICKHOUSE_DATABASE key should go before the CLICKHOUSE_EXTERNAL_PASSWORD key

(UnorderedKey)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Vercel Agent Review
🔇 Additional comments (12)
apps/e2e/tests/snapshot-serializer.ts (1)

71-72: LGTM!

Adding timing fields to stripFields is appropriate for test stability, as these values are non-deterministic and would cause snapshot tests to fail.

apps/backend/.env.development (1)

73-80: LGTM!

The ClickHouse environment variables follow the established patterns in this file. The port interpolation and password placeholder conventions are consistent with other services.

Note: The static analysis warnings about SubstitutionKey and UnorderedKey can be safely ignored—these are false positives given the standard patterns used throughout this .env file.

apps/dev-launchpad/public/index.html (2)

132-132: LGTM!

The ClickHouse background service entry is consistent with other services and correctly uses port suffix 33.


304-312: LGTM!

The ClickHouse app tile follows the established pattern and correctly references port suffix 33, matching the Docker service configuration.

apps/backend/scripts/db-migrations.ts (2)

11-11: LGTM!

The import is correctly structured.


155-156: LGTM!

Running ClickHouse migrations after Prisma migrations complete is the correct sequencing. The async/await usage is proper.

apps/backend/src/lib/clickhouse.tsx (1)

11-19: LGTM!

The client factory function correctly handles both admin and external authentication modes with optional timeout configuration.

docker/dependencies/docker.compose.yaml (1)

225-241: ClickHouse 25.10 is a stable release (verified Oct 31, 2025), and a patch update (25.10.1) is also available.

The image version specified in the docker-compose configuration is current, well-supported, and appropriate for use. No concerns with the version choice or service configuration.

apps/backend/package.json (1)

46-46: @clickhouse/client version 1.14.0 is current and has no known security vulnerabilities.

Version 1.14.0 is the latest stable release (released November 20, 2025), and the ^1.14.0 specifier is appropriate. No known security vulnerabilities were identified for the @clickhouse/client npm package.

apps/e2e/tests/backend/endpoints/api/v1/analytics-query.test.ts (1)

217-244: LGTM!

The timeout test using SELECT sleep(3) with a 100ms timeout is a reliable way to verify timeout handling.

packages/stack-shared/src/known-errors.tsx (2)

1632-1652: LGTM!

The new AnalyticsQueryTimeout and AnalyticsQueryError constructors follow the established pattern for known errors. The HTTP 400 status is appropriate, and the constructorArgsFromJson implementations correctly deserialize the error details.


1784-1785: LGTM!

The new error types are properly exported in the KnownErrors object.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
apps/backend/scripts/clickhouse-migrations.ts (1)

4-23: Migration GRANT targets a likely non-existent table and hardcodes the external user

This script still has the two issues called out in earlier reviews:

  • GRANT SELECT ON analytics.allowed_table1 TO limited_user; will fail if analytics.allowed_table1 doesn’t exist yet (which seems likely given the TODO about creating migration files).
  • The external user name is hardcoded as limited_user, even though STACK_CLICKHOUSE_EXTERNAL_USER exists and is used elsewhere; that can drift.

You could make this more robust along these lines:

 export async function runClickhouseMigrations() {
   console.log("Running Clickhouse migrations...");
   const client = createClickhouseClient("admin");
-  const clickhouseExternalPassword = getEnvVariable("STACK_CLICKHOUSE_EXTERNAL_PASSWORD");
-  // todo: create migration files
-  await client.exec({
-    query: "CREATE USER IF NOT EXISTS limited_user IDENTIFIED WITH plaintext_password BY {clickhouseExternalPassword:String}",
-    query_params: { clickhouseExternalPassword },
-  });
-  const queries = [
-    "GRANT SELECT ON analytics.allowed_table1 TO limited_user;",
-    "REVOKE ALL ON system.* FROM limited_user;",
-    "REVOKE CREATE, ALTER, DROP, INSERT ON *.* FROM limited_user;"
-  ];
-  for (const query of queries) {
-    console.log(query);
-    await client.exec({ query });
-  }
-  console.log("Clickhouse migrations complete");
-  await client.close();
+  const externalUser = getEnvVariable("STACK_CLICKHOUSE_EXTERNAL_USER", "limited_user");
+  const clickhouseExternalPassword = getEnvVariable("STACK_CLICKHOUSE_EXTERNAL_PASSWORD");
+
+  try {
+    // TODO: create proper ClickHouse schema migrations
+    await client.exec({
+      query: "CREATE USER IF NOT EXISTS {externalUser:Identifier} IDENTIFIED WITH plaintext_password BY {clickhouseExternalPassword:String}",
+      query_params: { externalUser, clickhouseExternalPassword },
+    });
+
+    const queries = [
+      // Enable GRANTs once the target tables exist.
+      // "GRANT SELECT ON analytics.some_table TO {externalUser:Identifier};",
+      `REVOKE ALL ON system.* FROM ${externalUser};`,
+      `REVOKE CREATE, ALTER, DROP, INSERT ON *.* FROM ${externalUser};`,
+    ];
+
+    for (const query of queries) {
+      console.log(query);
+      await client.exec({ query });
+    }
+
+    console.log("Clickhouse migrations complete");
+  } finally {
+    await client.close();
+  }
 }

Adjust the GRANT line once your analytics tables are in place.

apps/backend/src/lib/clickhouse.tsx (1)

22-55: Fix getQueryTimingStats typing and guard against missing query_log rows

This function still has the issues called out in earlier reviews:

  • profile.json is typed as if it returns a flat { cpu_time_ms, wall_clock_time_ms }, but you then access stats.data[0]; this doesn’t match the ClickHouse JSON shape and will likely fail type-checking.
  • At runtime, system.query_log can legitimately return an empty data array (log entry not flushed yet), making stats.data[0] undefined and crashing callers that assume stats are always present.

A safer version would be:

-export const getQueryTimingStats = async (client: ClickHouseClient, queryId: string) => {
+export const getQueryTimingStats = async (
+  client: ClickHouseClient,
+  queryId: string,
+): Promise<{ cpu_time_ms: number; wall_clock_time_ms: number }> => {
@@
-  const stats = await profile.json<{
-    cpu_time_ms: number,
-    wall_clock_time_ms: number,
-  }>();
-  return stats.data[0];
+  const stats = await profile.json<{
+    data: Array<{
+      cpu_time_ms: number;
+      wall_clock_time_ms: number;
+    }>;
+  }>();
+
+  if (!stats.data || stats.data.length === 0) {
+    throw new Error(
+      `Query timing stats not found for query_id=${queryId}; query_log entry may not be available yet.`,
+    );
+  }
+
+  return stats.data[0];
 };

Callers can then convert this error into a KnownErrors.AnalyticsQueryError or similar, as appropriate for the API surface.

🧹 Nitpick comments (2)
.github/workflows/docker-server-test.yaml (1)

26-30: Consider adding a lightweight readiness check for ClickHouse

The container may not always be ready within 5 seconds, especially on a busy CI runner. A transient slow start would make this job flaky.

Consider replacing the fixed sleep with a small readiness loop, for example:

      - name: Setup clickhouse
        run: |
          docker run -d --name clickhouse \
            -e CLICKHOUSE_DB=analytics \
            -e CLICKHOUSE_USER=stackframe \
            -e CLICKHOUSE_PASSWORD=password \
            -p 8133:8123 clickhouse/clickhouse-server:25.10

          for i in {1..30}; do
            if docker exec clickhouse clickhouse-client --query "SELECT 1" >/dev/null 2>&1; then
              echo "ClickHouse is ready"
              break
            fi
            echo "Waiting for ClickHouse..."
            sleep 1
          done

          docker logs clickhouse

</blockquote></details>
<details>
<summary>apps/backend/.env.development (1)</summary><blockquote>

`74-80`: **ClickHouse env block looks consistent with backend usage**

The new `STACK_CLICKHOUSE_*` variables line up with `createClickhouseClient` and the CI ClickHouse setup (DB `analytics`, admin user `stackframe`, external user `limited_user`), so this should wire up cleanly for local dev.

The `dotenv-linter` warnings here (substitution and key ordering) are stylistic only; the same `${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}` pattern is already used above. You can reorder the keys if you want to silence the linter, but it’s not functionally required.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used**: CodeRabbit UI

**Review profile**: CHILL

**Plan**: Pro

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 0c088962934cdf829c9e6b282d2fa5a4e07723a3 and 41287db62997e2a54a1084037a90136517fcbbe5.

</details>

<details>
<summary>📒 Files selected for processing (5)</summary>

* `.github/workflows/docker-server-test.yaml` (1 hunks)
* `apps/backend/.env.development` (1 hunks)
* `apps/backend/scripts/clickhouse-migrations.ts` (1 hunks)
* `apps/backend/src/lib/clickhouse.tsx` (1 hunks)
* `docker/server/.env.example` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>📓 Path-based instructions (2)</summary>

<details>
<summary>**/*.{ts,tsx}</summary>


**📄 CodeRabbit inference engine (AGENTS.md)**

> `**/*.{ts,tsx}`: Ensure code passes `pnpm typecheck` for TypeScript type validation
> Never use `toast` for blocking alerts and errors; use alerts instead as they are more visible to users
> Keep hover/click transitions snappy and fast; apply transitions after the action (e.g., smooth fade-out on hover end) rather than delaying actions with pre-transitions to avoid sluggish UI
> Never use try-catch-all, never void a promise, never use .catch(console.error) or similar; use `runAsynchronously` or `runAsynchronouslyWithAlert` for error handling instead of try-catch blocks
> Use loading indicators instead of try-catch blocks for asynchronous UI operations; button components like <Button> support async callbacks with built-in loading state
> Use ES6 maps instead of records wherever possible

Files:
- `apps/backend/src/lib/clickhouse.tsx`
- `apps/backend/scripts/clickhouse-migrations.ts`

</details>
<details>
<summary>apps/**/*.{ts,tsx}</summary>


**📄 CodeRabbit inference engine (AGENTS.md)**

> Prefer client components and dynamic functions like `usePathname` over Next.js dynamic functions to keep pages static; avoid using `await params` or similar dynamic patterns

Files:
- `apps/backend/src/lib/clickhouse.tsx`
- `apps/backend/scripts/clickhouse-migrations.ts`

</details>

</details><details>
<summary>🧠 Learnings (1)</summary>

<details>
<summary>📚 Learning: 2025-11-25T02:09:03.104Z</summary>

Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.104Z
Learning: Applies to packages/stack-shared/src/config/** : When making backwards-incompatible changes to the config schema, update the migration functions in packages/stack-shared/src/config/schema.ts


**Applied to files:**
- `apps/backend/scripts/clickhouse-migrations.ts`

</details>

</details><details>
<summary>🧬 Code graph analysis (2)</summary>

<details>
<summary>apps/backend/src/lib/clickhouse.tsx (1)</summary><blockquote>

<details>
<summary>packages/stack-shared/src/utils/env.tsx (1)</summary>

* `getEnvVariable` (16-58)

</details>

</blockquote></details>
<details>
<summary>apps/backend/scripts/clickhouse-migrations.ts (2)</summary><blockquote>

<details>
<summary>apps/backend/src/lib/clickhouse.tsx (1)</summary>

* `createClickhouseClient` (11-19)

</details>
<details>
<summary>packages/stack-shared/src/utils/env.tsx (1)</summary>

* `getEnvVariable` (16-58)

</details>

</blockquote></details>

</details><details>
<summary>🪛 dotenv-linter (4.0.0)</summary>

<details>
<summary>docker/server/.env.example</summary>

[warning] 20-20: [UnorderedKey] The STACK_CLICKHOUSE_ADMIN_PASSWORD key should go before the STACK_CLICKHOUSE_URL key

(UnorderedKey)

---

[warning] 21-21: [UnorderedKey] The STACK_CLICKHOUSE_EXTERNAL_PASSWORD key should go before the STACK_CLICKHOUSE_URL key

(UnorderedKey)

</details>
<details>
<summary>apps/backend/.env.development</summary>

[warning] 75-75: [SubstitutionKey] The STACK_CLICKHOUSE_URL key is not assigned properly

(SubstitutionKey)

---

[warning] 76-76: [UnorderedKey] The STACK_CLICKHOUSE_ADMIN_USER key should go before the STACK_CLICKHOUSE_URL key

(UnorderedKey)

---

[warning] 77-77: [UnorderedKey] The STACK_CLICKHOUSE_ADMIN_PASSWORD key should go before the STACK_CLICKHOUSE_ADMIN_USER key

(UnorderedKey)

---

[warning] 78-78: [UnorderedKey] The STACK_CLICKHOUSE_EXTERNAL_USER key should go before the STACK_CLICKHOUSE_URL key

(UnorderedKey)

---

[warning] 79-79: [UnorderedKey] The STACK_CLICKHOUSE_EXTERNAL_PASSWORD key should go before the STACK_CLICKHOUSE_EXTERNAL_USER key

(UnorderedKey)

---

[warning] 80-80: [UnorderedKey] The STACK_CLICKHOUSE_DATABASE key should go before the STACK_CLICKHOUSE_EXTERNAL_PASSWORD key

(UnorderedKey)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)</summary>

* GitHub Check: Vercel Agent Review
* GitHub Check: docker
* GitHub Check: restart-dev-and-test-with-custom-base-port
* GitHub Check: check_prisma_migrations (22.x)
* GitHub Check: setup-tests
* GitHub Check: build (22.x)
* GitHub Check: lint_and_build (latest)
* GitHub Check: all-good
* GitHub Check: build (22.x)
* GitHub Check: restart-dev-and-test
* GitHub Check: build (22.x)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
docker/server/.env.example (1)

19-21: Reorder ClickHouse keys for consistency with dotenv-linter and env-var conventions.

The three ClickHouse environment variables should be in alphabetical order to match the Postgres connection strings pattern and resolve the dotenv-linter UnorderedKey warnings.

Apply this diff:

-STACK_CLICKHOUSE_URL=http://host.docker.internal:8133
 STACK_CLICKHOUSE_ADMIN_PASSWORD=password
 STACK_CLICKHOUSE_EXTERNAL_PASSWORD=password
+STACK_CLICKHOUSE_URL=http://host.docker.internal:8133
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 41287db and 1320221.

📒 Files selected for processing (1)
  • docker/server/.env.example (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-25T02:09:03.104Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.104Z
Learning: When making dashboard changes, provide deep links to the dashboard page in the format `http://localhost:<$NEXT_PUBLIC_STACK_PORT_PREFIX>01/projects/-selector-/...` (or use a.localhost, b.localhost, c.localhost for port prefixes 91, 92, 93)

Applied to files:

  • docker/server/.env.example
🪛 dotenv-linter (4.0.0)
docker/server/.env.example

[warning] 20-20: [UnorderedKey] The STACK_CLICKHOUSE_ADMIN_PASSWORD key should go before the STACK_CLICKHOUSE_URL key

(UnorderedKey)


[warning] 21-21: [UnorderedKey] The STACK_CLICKHOUSE_EXTERNAL_PASSWORD key should go before the STACK_CLICKHOUSE_URL key

(UnorderedKey)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Vercel Agent Review
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: build (22.x)
  • GitHub Check: build (22.x)
  • GitHub Check: setup-tests
  • GitHub Check: build (22.x)
  • GitHub Check: all-good
  • GitHub Check: restart-dev-and-test
  • GitHub Check: docker
🔇 Additional comments (1)
docker/server/.env.example (1)

19-21: ✓ Critical issue resolved: ClickHouse URL correctly targets host-exposed service.

The change from localhost:8133 to host.docker.internal:8133 ensures the server container correctly reaches the ClickHouse service. This aligns with the Postgres connection pattern and will allow ClickHouse operations to function properly in Docker environments.

@N2D4 N2D4 self-requested a review November 26, 2025 18:44
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
apps/backend/scripts/clickhouse-migrations.ts (2)

14-14: GRANT statement references non-existent table.

This grants SELECT on analytics.allowed_table1, which doesn't exist yet and will cause the migration to fail.

Comment or remove this line until the table is created in a prior migration step.


22-23: Move client cleanup to a finally block.

If any query fails before line 23, the client won't be closed, leaving the connection open.

Apply this diff to guarantee cleanup:

-  console.log("Clickhouse migrations complete");
-  await client.close();
+  try {
+    // existing migration logic here
+    console.log("Clickhouse migrations complete");
+  } finally {
+    await client.close();
+  }

Note: Per coding guidelines, avoid try-catch-all for error handling. Let errors propagate naturally while ensuring cleanup in the finally block.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0077d16 and 62c6646.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (2)
  • apps/backend/package.json (1 hunks)
  • apps/backend/scripts/clickhouse-migrations.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: For blocking alerts and errors, never use toast, as they are easily missed by the user. Instead, use alerts
Keep hover/click transitions snappy and fast. Don't delay the action with a pre-transition; apply transitions after the action instead
NEVER try-catch-all, NEVER void a promise, and NEVER .catch(console.error). Use runAsynchronously or runAsynchronouslyWithAlert instead for asynchronous error handling
When creating hover transitions, avoid hover-enter transitions and just use hover-exit transitions (e.g., transition-colors hover:transition-none)
Use ES6 maps instead of records wherever possible

Files:

  • apps/backend/scripts/clickhouse-migrations.ts
🧠 Learnings (1)
📚 Learning: 2025-11-28T21:21:39.123Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-28T21:21:39.123Z
Learning: Applies to packages/stack-shared/src/config/schema.ts : Whenever making backwards-incompatible changes to the config schema, update the migration functions in `packages/stack-shared/src/config/schema.ts`

Applied to files:

  • apps/backend/scripts/clickhouse-migrations.ts
🧬 Code graph analysis (1)
apps/backend/scripts/clickhouse-migrations.ts (2)
apps/backend/src/lib/clickhouse.tsx (1)
  • createClickhouseClient (11-19)
packages/stack-shared/src/utils/env.tsx (1)
  • getEnvVariable (16-58)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Vercel Agent Review
  • GitHub Check: docker
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: build (22.x)
  • GitHub Check: build (22.x)
  • GitHub Check: setup-tests
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: restart-dev-and-test
  • GitHub Check: all-good
  • GitHub Check: build (22.x)
🔇 Additional comments (1)
apps/backend/package.json (1)

46-46: Version 1.14.0 is current and has no known vulnerabilities.

The @clickhouse/client package version 1.14.0 is the latest available on npm with no direct security vulnerabilities reported in Snyk or other vulnerability databases.

@cmux-agent
Copy link

cmux-agent bot commented Dec 8, 2025

Older cmux preview screenshots (latest comment is below)

Preview Screenshots

Open Workspace (1 hr expiry) · Open Dev Browser (1 hr expiry) · Open Diff Heatmap

Captured 3 screenshots for commit 20b597f (2025-12-08 18:39:32.957 UTC).

Element screenshot of the new ClickHouse service tile at port 8133

clickhouse-tile.png

Full page screenshot of the Dev Launchpad showing the new ClickHouse and MCPJam Inspector service tiles added in this PR, along with ClickHouse in the Background services list

dev-launchpad-full.png

Element screenshot of the new MCPJam Inspector service tile at port 8126

mcpjam-inspector-tile.png


Generated by cmux preview system

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
.github/workflows/docker-server-build-run.yaml (1)

26-30: ClickHouse setup step looks good and maintains consistency.

The step is well-configured:

  • Uses the same ClickHouse version (25.10) as the docker-compose file for consistency
  • Port mapping (8133:8123) aligns with the development environment configuration
  • Environment variables match those in .env.development
  • Logging output helps with debugging startup issues

Consider whether 5 seconds is sufficient for ClickHouse to fully initialize. Depending on the CI environment performance, you might want to add a health check loop similar to the "Check server health" step, or increase the sleep duration to ensure ClickHouse is ready before subsequent steps.

apps/backend/.env.development (1)

75-82: Consider alphabetically reordering the ClickHouse configuration keys for improved maintainability.

While the variable substitution pattern is already supported via the backend's custom environment variable handling, the ClickHouse keys should follow alphabetical order: STACK_CLICKHOUSE_ADMIN_PASSWORD, STACK_CLICKHOUSE_ADMIN_USER, STACK_CLICKHOUSE_DATABASE, STACK_CLICKHOUSE_EXTERNAL_PASSWORD, STACK_CLICKHOUSE_EXTERNAL_USER, STACK_CLICKHOUSE_URL.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 20b597f and 73407b3.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (6)
  • .github/workflows/docker-server-build-run.yaml (1 hunks)
  • apps/backend/.env.development (1 hunks)
  • apps/backend/package.json (1 hunks)
  • apps/dev-launchpad/public/index.html (2 hunks)
  • docker/dependencies/docker.compose.yaml (2 hunks)
  • packages/stack-shared/src/known-errors.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/backend/package.json
  • apps/dev-launchpad/public/index.html
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Always add new E2E tests when changing the API or SDK interface
For blocking alerts and errors, never use toast; use alerts instead as they are less easily missed by the user
NEVER try-catch-all, NEVER void a promise, and NEVER .catch(console.error); use loading indicators and async callbacks instead, or use runAsynchronously/runAsynchronouslyWithAlert for error handling
Use ES6 maps instead of records wherever you can

Files:

  • packages/stack-shared/src/known-errors.tsx
**/*.{ts,tsx,css}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,css}: Keep hover/click transitions snappy and fast; avoid fade-in delays on hover. Apply transitions after action completion instead, like smooth fade-out when hover ends
Use hover-exit transitions instead of hover-enter transitions; for example, use 'transition-colors hover:transition-none' instead of fade-in on hover

Files:

  • packages/stack-shared/src/known-errors.tsx
{.env*,**/*.{ts,tsx,js}}

📄 CodeRabbit inference engine (AGENTS.md)

Prefix environment variables with STACK_ (or NEXT_PUBLIC_STACK_ if public) so changes are picked up by Turborepo and improves readability

Files:

  • packages/stack-shared/src/known-errors.tsx
🧠 Learnings (1)
📚 Learning: 2025-12-04T18:03:49.984Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T18:03:49.984Z
Learning: Applies to {.env*,**/*.{ts,tsx,js}} : Prefix environment variables with STACK_ (or NEXT_PUBLIC_STACK_ if public) so changes are picked up by Turborepo and improves readability

Applied to files:

  • apps/backend/.env.development
🪛 dotenv-linter (4.0.0)
apps/backend/.env.development

[warning] 77-77: [SubstitutionKey] The STACK_CLICKHOUSE_URL key is not assigned properly

(SubstitutionKey)


[warning] 78-78: [UnorderedKey] The STACK_CLICKHOUSE_ADMIN_USER key should go before the STACK_CLICKHOUSE_URL key

(UnorderedKey)


[warning] 79-79: [UnorderedKey] The STACK_CLICKHOUSE_ADMIN_PASSWORD key should go before the STACK_CLICKHOUSE_ADMIN_USER key

(UnorderedKey)


[warning] 80-80: [UnorderedKey] The STACK_CLICKHOUSE_EXTERNAL_USER key should go before the STACK_CLICKHOUSE_URL key

(UnorderedKey)


[warning] 81-81: [UnorderedKey] The STACK_CLICKHOUSE_EXTERNAL_PASSWORD key should go before the STACK_CLICKHOUSE_EXTERNAL_USER key

(UnorderedKey)


[warning] 82-82: [UnorderedKey] The STACK_CLICKHOUSE_DATABASE key should go before the STACK_CLICKHOUSE_EXTERNAL_PASSWORD key

(UnorderedKey)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: Vercel Agent Review
  • GitHub Check: build (22.x)
  • GitHub Check: setup-tests-with-custom-base-port
  • GitHub Check: setup-tests
  • GitHub Check: restart-dev-and-test
  • GitHub Check: build (22.x)
  • GitHub Check: docker
  • GitHub Check: all-good
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: E2E Tests (Node 22.x, Freestyle prod)
  • GitHub Check: E2E Tests (Node 22.x, Freestyle mock)
  • GitHub Check: check_prisma_migrations (22.x)
🔇 Additional comments (2)
docker/dependencies/docker.compose.yaml (1)

208-226: ClickHouse service configuration looks good.

The ClickHouse service is well-configured with:

  • Specific image version (25.10) for reproducibility
  • Proper environment variables for database initialization
  • Persistent volume mount
  • Increased ulimits for production-like behavior
packages/stack-shared/src/known-errors.tsx (1)

1636-1656: LGTM! Analytics error constructors follow established patterns.

The new AnalyticsQueryTimeout and AnalyticsQueryError error constructors are well-implemented:

  • Follow the existing createKnownErrorConstructor pattern consistently
  • Use appropriate 400 status codes for client errors
  • Include descriptive messages and properly structured details objects
  • Correctly implement constructorArgsFromJson for deserialization
  • Properly exported in the KnownErrors collection

Also applies to: 1788-1789

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
apps/backend/scripts/clickhouse-migrations.ts (2)

14-14: Migration will fail: analytics.allowed_table1 doesn't exist.

This GRANT references a table that hasn't been created. The migration will fail at runtime. Per past discussion, this is noted as temporary, but it should be commented out or removed until the table exists.


9-21: Add try-finally to ensure client cleanup on failure.

If any client.exec() call throws, client.close() on line 23 won't be reached, causing a resource leak. Wrap the migration logic in a try-finally block.

 export async function runClickhouseMigrations() {
   console.log("[Clickhouse] Running Clickhouse migrations...");
-  const client = clickhouseAdminClient;
+  const client = createClickhouseClient("admin");
   const clickhouseExternalPassword = getEnvVariable("STACK_CLICKHOUSE_EXTERNAL_PASSWORD");
-  // todo: create migration files
-  await client.exec({
-    query: "CREATE USER IF NOT EXISTS limited_user IDENTIFIED WITH plaintext_password BY {clickhouseExternalPassword:String}",
-    query_params: { clickhouseExternalPassword },
-  });
-  const queries = [
-    "GRANT SELECT ON analytics.allowed_table1 TO limited_user;",
-    "REVOKE ALL ON system.* FROM limited_user;",
-    "REVOKE CREATE, ALTER, DROP, INSERT ON *.* FROM limited_user;"
-  ];
-  for (const query of queries) {
-    console.log("[Clickhouse] " + query);
-    await client.exec({ query });
+  try {
+    await client.exec({
+      query: "CREATE USER IF NOT EXISTS limited_user IDENTIFIED WITH plaintext_password BY {clickhouseExternalPassword:String}",
+      query_params: { clickhouseExternalPassword },
+    });
+    const queries = [
+      "GRANT SELECT ON analytics.allowed_table1 TO limited_user;",
+      "REVOKE ALL ON system.* FROM limited_user;",
+      "REVOKE CREATE, ALTER, DROP, INSERT ON *.* FROM limited_user;"
+    ];
+    for (const query of queries) {
+      console.log("[Clickhouse] " + query);
+      await client.exec({ query });
+    }
+    console.log("[Clickhouse] Clickhouse migrations complete");
+  } finally {
+    await client.close();
   }
-  console.log("[Clickhouse] Clickhouse migrations complete");
-  await client.close();
 }
🧹 Nitpick comments (2)
apps/backend/src/app/api/latest/internal/analytics/query/route.ts (1)

52-53: Consider wrapping getQueryTimingStats errors.

If getQueryTimingStats fails (e.g., query log entry not found), it throws a StackAssertionError which will propagate as an internal error rather than a user-friendly AnalyticsQueryError. Consider wrapping this call for consistent error responses.

     const rows = await resultSet.data.json<Record<string, unknown>[]>();
-    const stats = await getQueryTimingStats(client, queryId);
+    let stats;
+    try {
+      stats = await getQueryTimingStats(client, queryId);
+    } catch (error) {
+      const message = error instanceof Error ? error.message : "Failed to retrieve query timing statistics";
+      throw new KnownErrors.AnalyticsQueryError(message);
+    }
apps/e2e/tests/backend/endpoints/api/v1/analytics-query.test.ts (1)

217-239: Test timeout behavior may be unstable across ClickHouse versions

The SELECT sleep(3) with 100ms timeout can be affected by ClickHouse versions where sleep queries behave inconsistently with max_execution_time enforcement. Recent ClickHouse versions show timeout behavior inconsistencies where max_execution_time is sometimes treated differently.

Consider adding an inline comment explaining that the test expects ANALYTICS_QUERY_ERROR due to server-side timeout enforcement, and note any specific ClickHouse version constraints if the timeout behavior is known to be reliable only in certain versions.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 73407b3 and f6fb8cf.

📒 Files selected for processing (6)
  • apps/backend/scripts/clickhouse-migrations.ts (1 hunks)
  • apps/backend/src/app/api/latest/internal/analytics/query/route.ts (1 hunks)
  • apps/backend/src/lib/clickhouse.tsx (1 hunks)
  • apps/dev-launchpad/public/index.html (2 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/analytics-query.test.ts (1 hunks)
  • docker/dependencies/docker.compose.yaml (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/dev-launchpad/public/index.html
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Always add new E2E tests when changing the API or SDK interface
For blocking alerts and errors, never use toast; use alerts instead as they are less easily missed by the user
NEVER try-catch-all, NEVER void a promise, and NEVER .catch(console.error); use loading indicators and async callbacks instead, or use runAsynchronously/runAsynchronouslyWithAlert for error handling
Use ES6 maps instead of records wherever you can

Files:

  • apps/backend/src/app/api/latest/internal/analytics/query/route.ts
  • apps/e2e/tests/backend/endpoints/api/v1/analytics-query.test.ts
  • apps/backend/scripts/clickhouse-migrations.ts
  • apps/backend/src/lib/clickhouse.tsx
**/*.{ts,tsx,css}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,css}: Keep hover/click transitions snappy and fast; avoid fade-in delays on hover. Apply transitions after action completion instead, like smooth fade-out when hover ends
Use hover-exit transitions instead of hover-enter transitions; for example, use 'transition-colors hover:transition-none' instead of fade-in on hover

Files:

  • apps/backend/src/app/api/latest/internal/analytics/query/route.ts
  • apps/e2e/tests/backend/endpoints/api/v1/analytics-query.test.ts
  • apps/backend/scripts/clickhouse-migrations.ts
  • apps/backend/src/lib/clickhouse.tsx
apps/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

NEVER use Next.js dynamic functions if you can avoid them; prefer using client components and hooks like usePathname instead of await params to keep pages static

Files:

  • apps/backend/src/app/api/latest/internal/analytics/query/route.ts
  • apps/e2e/tests/backend/endpoints/api/v1/analytics-query.test.ts
  • apps/backend/scripts/clickhouse-migrations.ts
  • apps/backend/src/lib/clickhouse.tsx
{.env*,**/*.{ts,tsx,js}}

📄 CodeRabbit inference engine (AGENTS.md)

Prefix environment variables with STACK_ (or NEXT_PUBLIC_STACK_ if public) so changes are picked up by Turborepo and improves readability

Files:

  • apps/backend/src/app/api/latest/internal/analytics/query/route.ts
  • apps/e2e/tests/backend/endpoints/api/v1/analytics-query.test.ts
  • apps/backend/scripts/clickhouse-migrations.ts
  • apps/backend/src/lib/clickhouse.tsx
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer .toMatchInlineSnapshot over other selectors in tests when possible; check snapshot-serializer.ts for formatting details

Files:

  • apps/e2e/tests/backend/endpoints/api/v1/analytics-query.test.ts
apps/e2e/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Always add new E2E tests when changing API or SDK interface; err on the side of creating too many tests due to the critical nature of the industry

Files:

  • apps/e2e/tests/backend/endpoints/api/v1/analytics-query.test.ts
apps/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Check existing apps for inspiration when implementing new apps or pages; update apps-frontend.tsx and apps-config.ts to add new apps

Files:

  • apps/backend/src/lib/clickhouse.tsx
🧠 Learnings (5)
📚 Learning: 2025-12-17T01:23:07.460Z
Learnt from: N2D4
Repo: stack-auth/stack-auth PR: 1032
File: apps/backend/src/app/api/latest/analytics/query/route.tsx:20-20
Timestamp: 2025-12-17T01:23:07.460Z
Learning: In apps/backend/src/app/api/latest/analytics/query/route.tsx, the include_all_branches field controls which ClickHouse user type is used: when true, use "admin" authType for access to all branches; when false (default), use "external" authType for limited/filtered branch access.

Applied to files:

  • apps/backend/src/app/api/latest/internal/analytics/query/route.ts
  • apps/backend/scripts/clickhouse-migrations.ts
  • apps/backend/src/lib/clickhouse.tsx
📚 Learning: 2025-12-04T18:03:49.984Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T18:03:49.984Z
Learning: Applies to **/*.{ts,tsx} : NEVER try-catch-all, NEVER void a promise, and NEVER .catch(console.error); use loading indicators and async callbacks instead, or use runAsynchronously/runAsynchronouslyWithAlert for error handling

Applied to files:

  • apps/backend/src/app/api/latest/internal/analytics/query/route.ts
📚 Learning: 2025-12-04T18:03:49.984Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T18:03:49.984Z
Learning: Applies to apps/e2e/**/*.{ts,tsx} : Always add new E2E tests when changing API or SDK interface; err on the side of creating too many tests due to the critical nature of the industry

Applied to files:

  • apps/e2e/tests/backend/endpoints/api/v1/analytics-query.test.ts
📚 Learning: 2025-12-04T18:03:49.984Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T18:03:49.984Z
Learning: Applies to **/*.{ts,tsx} : Always add new E2E tests when changing the API or SDK interface

Applied to files:

  • apps/e2e/tests/backend/endpoints/api/v1/analytics-query.test.ts
📚 Learning: 2025-12-04T18:03:49.984Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T18:03:49.984Z
Learning: Applies to packages/stack-shared/src/config/schema.ts : Whenever making backwards-incompatible changes to the config schema, update the migration functions in packages/stack-shared/src/config/schema.ts

Applied to files:

  • apps/backend/scripts/clickhouse-migrations.ts
🧬 Code graph analysis (2)
apps/backend/src/app/api/latest/internal/analytics/query/route.ts (4)
apps/backend/src/route-handlers/smart-route-handler.tsx (1)
  • createSmartRouteHandler (211-296)
packages/stack-shared/src/schema-fields.ts (8)
  • yupObject (247-251)
  • adminAuthTypeSchema (499-499)
  • adaptSchema (330-330)
  • yupBoolean (195-198)
  • yupString (187-190)
  • yupRecord (283-322)
  • yupMixed (210-212)
  • jsonSchema (410-410)
apps/backend/src/prisma-client.tsx (1)
  • client (133-138)
apps/backend/src/lib/clickhouse.tsx (2)
  • clickhouseExternalClient (22-22)
  • getQueryTimingStats (24-60)
apps/backend/src/lib/clickhouse.tsx (2)
packages/stack-shared/src/utils/env.tsx (1)
  • getEnvVariable (16-58)
packages/stack-shared/src/utils/errors.tsx (1)
  • StackAssertionError (69-85)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: all-good
  • GitHub Check: Vercel Agent Review
  • GitHub Check: E2E Tests (Node 22.x, Freestyle prod)
  • GitHub Check: E2E Tests (Node 22.x, Freestyle mock)
  • GitHub Check: build (22.x)
  • GitHub Check: setup-tests-with-custom-base-port
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: build (22.x)
  • GitHub Check: setup-tests
  • GitHub Check: restart-dev-and-test
🔇 Additional comments (6)
docker/dependencies/docker.compose.yaml (1)

208-226: ClickHouse service configuration looks good.

The service is properly configured with appropriate ports, environment variables, volume mount, and ulimits for file descriptors. The port conflict with drizzle-gateway has been resolved by moving drizzle-gateway to port suffix 35.

apps/backend/src/app/api/latest/internal/analytics/query/route.ts (1)

8-67: Good use of singleton client pattern.

Using the pre-configured clickhouseExternalClient singleton avoids the per-request client creation and resource leak issues flagged in past reviews. The route structure follows the established patterns with proper schema validation.

apps/e2e/tests/backend/endpoints/api/v1/analytics-query.test.ts (1)

4-382: Comprehensive E2E test coverage for the analytics query endpoint.

The tests cover:

  • Basic query execution with admin access
  • Parameterized queries (single and multiple params)
  • Custom timeout handling
  • Required field validation
  • Invalid SQL handling
  • Multiple row results
  • Tenancy ID verification
  • Security restrictions (CREATE, system tables, KILL, INSERT)

This aligns well with the coding guidelines requiring E2E tests for API changes.

apps/backend/src/lib/clickhouse.tsx (3)

5-10: Environment variables correctly use STACK_ prefix.

The environment variables follow the coding guidelines with the STACK_CLICKHOUSE_* prefix, ensuring they're picked up by Turborepo and maintaining naming consistency.


52-59: Proper validation for query log results.

The code now validates that exactly one result is returned from the query log and throws a StackAssertionError with context if not, addressing the past review concern about undefined access.


21-22: Singleton clients created at module load time.

The singleton clients are instantiated when the module is imported. If required environment variables (like STACK_CLICKHOUSE_URL) are missing, this will throw at startup rather than at first use. This matches the pattern used elsewhere (e.g., Prisma client) and provides early failure detection.

@cmux-agent
Copy link

cmux-agent bot commented Dec 20, 2025

Preview Screenshots

Open Diff Heatmap

Preview screenshots are being captured...

Workspace and dev browser links will appear here once the preview environment is ready.


Generated by cmux preview system

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
apps/backend/scripts/clickhouse-migrations.ts (3)

6-6: Critical: Closing the singleton admin client breaks subsequent usage.

The code uses clickhouseAdminClient (a module-level singleton exported from @/lib/clickhouse and used elsewhere in the codebase, e.g., apps/backend/src/lib/events.tsx line 198), but then closes it at line 28. This will break all subsequent operations that depend on this shared client.

🔎 Proposed fix

Since this is a one-off migration script, either:

Option 1 (Recommended): Create a dedicated client instance for migrations and close it:

-  const client = clickhouseAdminClient;
+  const client = createClickhouseClient("admin");

Then keep the client.close() at line 28.

Option 2: If using the singleton intentionally, remove the close call:

   console.log("[Clickhouse] Clickhouse migrations complete");
-  await client.close();

Also applies to: 28-28


4-29: Critical: Missing error handling causes resource leaks and silent failures.

The migration function lacks try-catch-finally error handling. If any client.exec() call fails, the client connection will leak (won't be closed), and the error won't be properly logged or propagated.

🔎 Proposed fix

Wrap the migration logic in try-finally to ensure cleanup:

 export async function runClickhouseMigrations() {
   console.log("[Clickhouse] Running Clickhouse migrations...");
   const client = clickhouseAdminClient;
-  const clickhouseExternalPassword = getEnvVariable("STACK_CLICKHOUSE_EXTERNAL_PASSWORD");
-  await client.exec({
-    query: "CREATE USER IF NOT EXISTS limited_user IDENTIFIED WITH sha256_password BY {clickhouseExternalPassword:String}",
-    query_params: { clickhouseExternalPassword },
-  });
-  // todo: create migration files
-  await client.exec({ query: EXTERNAL_ANALYTICS_DB_SQL });
-  await client.exec({ query: EVENTS_TABLE_BASE_SQL });
-  await client.exec({ query: EVENTS_VIEW_SQL });
-  const queries = [
-    "REVOKE ALL PRIVILEGES ON *.* FROM limited_user;",
-    "REVOKE ALL FROM limited_user;",
-    "GRANT SELECT ON default.events TO limited_user;",
-  ];
-  await client.exec({
-    query: "CREATE ROW POLICY IF NOT EXISTS events_project_isolation ON default.events FOR SELECT USING project_id = getSetting('SQL_project_id') AND branch_id = getSetting('SQL_branch_id') TO limited_user",
-  });
-  for (const query of queries) {
-    await client.exec({ query });
+  try {
+    const clickhouseExternalPassword = getEnvVariable("STACK_CLICKHOUSE_EXTERNAL_PASSWORD");
+    await client.exec({
+      query: "CREATE USER IF NOT EXISTS limited_user IDENTIFIED WITH sha256_password BY {clickhouseExternalPassword:String}",
+      query_params: { clickhouseExternalPassword },
+    });
+    // todo: create migration files
+    await client.exec({ query: EXTERNAL_ANALYTICS_DB_SQL });
+    await client.exec({ query: EVENTS_TABLE_BASE_SQL });
+    await client.exec({ query: EVENTS_VIEW_SQL });
+    const queries = [
+      "REVOKE ALL PRIVILEGES ON *.* FROM limited_user;",
+      "REVOKE ALL FROM limited_user;",
+      "GRANT SELECT ON default.events TO limited_user;",
+    ];
+    await client.exec({
+      query: "CREATE ROW POLICY IF NOT EXISTS events_project_isolation ON default.events FOR SELECT USING project_id = getSetting('SQL_project_id') AND branch_id = getSetting('SQL_branch_id') TO limited_user",
+    });
+    for (const query of queries) {
+      await client.exec({ query });
+    }
+    console.log("[Clickhouse] Clickhouse migrations complete");
+  } catch (error) {
+    console.error("[Clickhouse] Migration failed:", error);
+    throw error;
+  } finally {
+    await client.close();
   }
-  console.log("[Clickhouse] Clickhouse migrations complete");
-  await client.close();
 }

Note: This still has the singleton closing issue from the previous comment.


21-23: Major: Row policy relies on custom settings without proper configuration.

The row policy uses getSetting('SQL_project_id') and getSetting('SQL_branch_id'), but there's no configuration to:

  1. Allow these custom settings to be changed in readonly mode
  2. Create a role with CHANGEABLE_IN_READONLY constraints for these settings
  3. Verify that server-side custom_settings_prefixes allows the SQL_ prefix

Without this configuration, the limited_user won't be able to set these custom settings, and the row policy won't function correctly, breaking tenant isolation.

🔎 Recommended approach

After creating the user, add role configuration:

   await client.exec({
     query: "CREATE USER IF NOT EXISTS limited_user IDENTIFIED WITH sha256_password BY {clickhouseExternalPassword:String}",
     query_params: { clickhouseExternalPassword },
   });
+  // Configure role to allow custom settings in readonly mode
+  await client.exec({
+    query: "CREATE ROLE IF NOT EXISTS limited_analytics_role SETTINGS SQL_project_id CHANGEABLE_IN_READONLY, SQL_branch_id CHANGEABLE_IN_READONLY",
+  });
+  await client.exec({
+    query: "GRANT limited_analytics_role TO limited_user",
+  });
+  await client.exec({
+    query: "SET DEFAULT ROLE limited_analytics_role TO limited_user",
+  });

Also, verify that the ClickHouse server configuration includes custom_settings_prefixes = 'SQL_' in the config file or environment.

Based on learnings from previous reviews on analytics query route.

🧹 Nitpick comments (1)
apps/backend/scripts/clickhouse-migrations.ts (1)

12-12: TODO: Implement proper migration file system.

The inline SQL constants and sequential execution don't provide versioning, rollback capability, or idempotent re-runs. Consider implementing a proper migration system with:

  • Numbered migration files (e.g., 001_create_events_table.sql)
  • Migration tracking table to record applied migrations
  • Ability to roll back or skip already-applied migrations

Do you want me to generate a migration framework implementation or open an issue to track this enhancement?

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 136b25f and 3760836.

📒 Files selected for processing (1)
  • apps/backend/scripts/clickhouse-migrations.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Always add new E2E tests when changing the API or SDK interface
For blocking alerts and errors, never use toast; use alerts instead as they are less easily missed by the user
NEVER try-catch-all, NEVER void a promise, and NEVER .catch(console.error); use loading indicators and async callbacks instead, or use runAsynchronously/runAsynchronouslyWithAlert for error handling
Use ES6 maps instead of records wherever you can

Files:

  • apps/backend/scripts/clickhouse-migrations.ts
**/*.{ts,tsx,css}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,css}: Keep hover/click transitions snappy and fast; avoid fade-in delays on hover. Apply transitions after action completion instead, like smooth fade-out when hover ends
Use hover-exit transitions instead of hover-enter transitions; for example, use 'transition-colors hover:transition-none' instead of fade-in on hover

Files:

  • apps/backend/scripts/clickhouse-migrations.ts
apps/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

NEVER use Next.js dynamic functions if you can avoid them; prefer using client components and hooks like usePathname instead of await params to keep pages static

Files:

  • apps/backend/scripts/clickhouse-migrations.ts
{.env*,**/*.{ts,tsx,js}}

📄 CodeRabbit inference engine (AGENTS.md)

Prefix environment variables with STACK_ (or NEXT_PUBLIC_STACK_ if public) so changes are picked up by Turborepo and improves readability

Files:

  • apps/backend/scripts/clickhouse-migrations.ts
🧠 Learnings (2)
📚 Learning: 2025-12-04T18:03:49.984Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T18:03:49.984Z
Learning: Applies to packages/stack-shared/src/config/schema.ts : Whenever making backwards-incompatible changes to the config schema, update the migration functions in packages/stack-shared/src/config/schema.ts

Applied to files:

  • apps/backend/scripts/clickhouse-migrations.ts
📚 Learning: 2025-12-17T01:23:15.483Z
Learnt from: N2D4
Repo: stack-auth/stack-auth PR: 1032
File: apps/backend/src/app/api/latest/analytics/query/route.tsx:20-20
Timestamp: 2025-12-17T01:23:15.483Z
Learning: In apps/backend/src/app/api/latest/analytics/query/route.tsx, the include_all_branches field controls which ClickHouse user type is used: when true, use "admin" authType for access to all branches; when false (default), use "external" authType for limited/filtered branch access.

Applied to files:

  • apps/backend/scripts/clickhouse-migrations.ts
🧬 Code graph analysis (1)
apps/backend/scripts/clickhouse-migrations.ts (2)
apps/backend/src/lib/clickhouse.tsx (1)
  • clickhouseAdminClient (21-21)
packages/stack-shared/src/utils/env.tsx (1)
  • getEnvVariable (16-58)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: Vercel Agent Review
  • GitHub Check: setup-tests
  • GitHub Check: E2E Tests (Node 22.x, Freestyle prod)
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: E2E Tests (Node 22.x, Freestyle mock)
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: setup-tests-with-custom-base-port
  • GitHub Check: build (22.x)
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: build (22.x)
  • GitHub Check: restart-dev-and-test
  • GitHub Check: docker
  • GitHub Check: all-good
🔇 Additional comments (4)
apps/backend/scripts/clickhouse-migrations.ts (4)

16-20: LGTM: Permission revocation and grant logic is sound.

The double revocation (REVOKE ALL PRIVILEGES followed by REVOKE ALL) ensures a clean slate, and the grant on default.events (the view) correctly restricts access to the filtered dataset through the row policy.


31-49: LGTM: Events table schema is well-designed.

The table structure is appropriate with:

  • LowCardinality(String) for event_type (good for cardinality optimization)
  • DateTime64(3, 'UTC') for timestamps with millisecond precision
  • JSON for flexible event data
  • Monthly partitioning by event_at for efficient data management
  • Sort order (project_id, branch_id, event_at) optimized for the row policy filters

13-15: LGTM: Database, table, and view creation order is correct.

The execution sequence properly handles dependencies:

  1. Creates analytics_internal database first
  2. Creates analytics_internal.events table
  3. Creates default.events view pointing to the table
  4. Applies row policy and grants (lines 21-26)

51-57: Row policies may not apply correctly through SQL SECURITY DEFINER views.

Row policies are applied when a user performs a SELECT query and restrict which rows are returned, but the interaction with DEFINER views is not straightforward. ClickHouse has known issues where DEFINER and SQL SECURITY DEFINER settings on certain view types are ignored in favor of invoker permissions. Ensure that the analytics_internal.events table has a row policy defined and test that it correctly filters results when queried through this DEFINER view with a limited_user account. Consider adding E2E tests to verify row isolation works as expected.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/backend/src/lib/tokens.tsx (1)

247-256: Avoid teamId: "" in analytics; empty string will be stored as a real value and break segmentation.

While I understand the concern about empty strings polluting analytics data, the suggested fix user.selected_team_id ?? null is incompatible with the current schema. The SessionActivityEventType schema defines teamId as yupString().optional().default(""), which does not accept null as a valid type—only strings or undefined.

Two better approaches:

  1. Use undefined to trigger the schema default (minimal change):

    teamId: user.selected_team_id || undefined,

    This lets the schema apply its intended default.

  2. Update the schema to support null (semantic improvement):

    • Change the schema to yupString().nullable().default(null)
    • Update the extraction logic in logEvent to handle null as a distinct value from empty string
    • Then use teamId: user.selected_team_id ?? null

Option 1 is the quickest fix; Option 2 would require coordinating schema changes but provides clearer semantics for "no team assigned" in your analytics backend.

🤖 Fix all issues with AI agents
In
`@apps/backend/src/app/api/latest/internal/clickhouse/migrate-events/route.tsx`:
- Around line 152-164: The migration currently blindly inserts rows
(rowsToInsert) via clickhouseClient.insert into analytics_internal.events,
causing duplicates on re-run; to fix, query ClickHouse for existing event
identifiers (e.g., event_id or the primary key used) for the same time range
before calling clickhouseClient.insert, filter rowsToInsert to remove any IDs
that already exist, update skipped_existing_events to reflect how many were
dropped, and then insert only the filtered set (adjust insertedRows
accordingly). Locate the insertion logic around rowsToInsert,
clickhouseClient.insert, insertedRows and skipped_existing_events and implement
the pre-insert existence check + in-memory filter using the same unique key(s)
as the table.
♻️ Duplicate comments (3)
apps/e2e/tests/backend/backend-helpers.ts (1)

97-97: CamelCase detection is now too narrow (misses common keys).

Anchoring the regex means only strings like aBcd match, so typical camelCase keys (e.g., myKeyValue) will no longer be detected. This weakens the snake_case assertion. Consider reverting to a broader pattern.
Line 97.

🛠️ Suggested fix
-      if (key.match(/^[a-z0-9][A-Z][a-z0-9]+$/) && !key.includes("_") && !["newUser", "afterCallbackRedirectUrl"].includes(key)) {
+      if (key.match(/[a-z0-9]+[A-Z][a-z0-9]+/) && !key.includes("_") && !["newUser", "afterCallbackRedirectUrl"].includes(key)) {
apps/backend/src/app/api/latest/internal/clickhouse/migrate-events/route.tsx (2)

41-41: Extract branchId from event data instead of hardcoding.

This hardcodes branchId to DEFAULT_BRANCH_ID, causing historical events with non-default branch IDs to be migrated with incorrect data. The extraction pattern used for other fields (projectId, userId, etc.) should also apply here.

Suggested fix
-  const branchId = DEFAULT_BRANCH_ID;
+  const branchId = typeof dataRecord.branchId === "string" ? dataRecord.branchId : DEFAULT_BRANCH_ID;

62-208: Add E2E tests for this migration endpoint.

Per repository guidelines, new API endpoints require E2E test coverage. Tests should cover: successful migration with pagination, invalid timestamp handling, cursor-based pagination behavior, and empty result handling.

🧹 Nitpick comments (4)
apps/backend/.env (1)

84-90: Optional: remove the extra blank line to avoid lint noise.
Keeps dotenv-linter clean and consistent.

🧹 Suggested cleanup
 STACK_CLICKHOUSE_EXTERNAL_PASSWORD=# a randomly generated secure string. The user account will be created automatically
-
-
+# (blank line removed)
apps/backend/src/app/api/latest/internal/clickhouse/migrate-events/route.tsx (3)

1-12: Unused import and prefer interface over type.

createClickhouseClient is imported but never used in this file. Also, per coding guidelines, prefer interface for defining object shapes.

Suggested fix
-import { clickhouseAdminClient, createClickhouseClient } from "@/lib/clickhouse";
+import { clickhouseAdminClient } from "@/lib/clickhouse";
 import { DEFAULT_BRANCH_ID } from "@/lib/tenancies";
 import { globalPrismaClient } from "@/prisma-client";
 import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler";
 import { yupNumber, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
 import { StatusError } from "@stackframe/stack-shared/dist/utils/errors";
 import type { Prisma } from "@/generated/prisma/client";

-type Cursor = {
-  created_at_millis: number,
-  id: string,
-};
+interface Cursor {
+  created_at_millis: number;
+  id: string;
+}

28-28: Add comment explaining the any type usage.

Per coding guidelines, when using any, leave a comment explaining why. The data field comes from Prisma's JSON type which doesn't have a strict schema.

Suggested fix
 const createClickhouseRows = (event: {
   id: string,
   systemEventTypeIds: string[],
-  data: any,
+  // eslint-disable-next-line `@typescript-eslint/no-explicit-any` -- Prisma JSON field has no schema; validated at runtime below
+  data: any,
   eventEndedAt: Date,
   eventStartedAt: Date,
   isWide: boolean,

176-187: Avoid non-null assertions; use explicit guards.

Per coding guidelines, prefer ?? throwErr(...) over non-null assertions. The progressCursorCreatedAt! assertions on lines 182-183 rely on the outer progressCursor check, but explicit handling is safer.

Suggested fix
-    const progressCursorCreatedAt = progressCursor ? new Date(progressCursor.created_at_millis) : null;
-    const remainingWhere = progressCursor ? {
+    const remainingWhere: Prisma.EventWhereInput = progressCursor ? {
+      AND: [
+        baseWhere,
+        {
+          OR: [
+            { createdAt: { gt: new Date(progressCursor.created_at_millis) } },
+            { createdAt: new Date(progressCursor.created_at_millis), id: { gt: progressCursor.id } },
+          ],
+        },
+      ],
+    } : baseWhere;
-      AND: [
-        baseWhere,
-        {
-          OR: [
-            { createdAt: { gt: progressCursorCreatedAt! } },
-            { createdAt: progressCursorCreatedAt!, id: { gt: progressCursor.id } },
-          ],
-        },
-      ],
-    } : baseWhere;

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In
`@apps/backend/src/app/api/latest/internal/clickhouse/migrate-events/route.tsx`:
- Around line 160-163: The ClickHouse insert configuration currently sets
clickhouse_settings.async_insert: 1 which makes failures silent; update the
settings object (clickhouse_settings) used in the migration handler (route.tsx)
to also include wait_for_async_insert: 1 so the client blocks until insert
feedback is available and errors surface; ensure the same change is applied
wherever async_insert is used (e.g., the events handler) to guarantee insert
failures are reported.
- Around line 146-167: The ClickHouse admin client returned by
getClickhouseAdminClient() is never closed, risking connection leaks; wrap the
usage of clickhouseClient (the variable created after isClickhouseConfigured())
in a try/finally (or try/catch/finally) and call await clickhouseClient.close()
(or the client’s appropriate shutdown method if different) in the finally block
after the insert (and any error handling), ensuring close is awaited even when
insert throws; keep the existing logic that computes rowsByEvent/rowsToInsert
and sets migratedEvents/insertedRows inside the try.
♻️ Duplicate comments (9)
apps/backend/scripts/db-migrations.ts (2)

161-162: Consider clarifying migration types in console output.

As noted in a past review, it would help to differentiate between Postgres and ClickHouse migrations in the console logs. The summary log at lines 141-159 only mentions "migrations" generically.


21-24: ClickHouse client is never closed.

The client created here maintains an HTTP connection pool that should be closed after use to prevent resource leaks. Either close it explicitly or reuse the pattern from runClickhouseMigrations which closes its client.

🔧 Proposed fix
   const clickhouseClient = getClickhouseClient();
   await clickhouseClient.command({ query: "DROP DATABASE IF EXISTS analytics_internal" });
   await clickhouseClient.command({ query: "CREATE DATABASE IF NOT EXISTS analytics_internal" });
+  await clickhouseClient.close();
 };
apps/backend/src/app/api/latest/internal/analytics/query/route.ts (3)

116-119: Unknown error codes should log to Sentry before returning generic message.

Per prior review feedback, when an error code is neither safe nor unsafe, the function returns null which triggers a StackAssertionError. Unknown codes should be logged for investigation while returning the safe default message to users.

🔧 Proposed fix
+import { captureError } from "@stackframe/stack-shared/dist/utils/errors";
+
 function getSafeClickhouseErrorMessage(error: unknown): string | null {
   ...
   if (UNSAFE_CLICKHOUSE_ERROR_CODES.includes(errorCode)) {
     return DEFAULT_CLICKHOUSE_ERROR_MESSAGE;
   }
-  return null;
+  // Unknown error code - log to Sentry for investigation but return safe message to user
+  captureError("unknown-clickhouse-error-code", new Error(`Unknown ClickHouse error code: ${errorCode}`), { error });
+  return DEFAULT_CLICKHOUSE_ERROR_MESSAGE;
 }

34-82: ClickHouse client is never closed.

The client created at line 41 via getClickhouseExternalClient() maintains an HTTP connection pool. Without calling client.close(), connections will leak. Wrap the logic in try-finally.

🔧 Proposed fix
   async handler({ body, auth }) {
     if (body.include_all_branches) {
       throw new StackAssertionError("include_all_branches is not supported yet");
     }
     if (!isClickhouseConfigured()) {
       throw new StackAssertionError("ClickHouse is not configured");
     }
     const client = getClickhouseExternalClient();
-    const queryId = randomUUID();
-    const resultSet = await Result.fromPromise(client.query({
-      ...
-    }));
-    ...
-    return { ... };
+    try {
+      const queryId = randomUUID();
+      const resultSet = await Result.fromPromise(client.query({
+        ...
+      }));
+      ...
+      return { ... };
+    } finally {
+      await client.close();
+    }
   },

113-114: Safe error codes with null message should fallback to default.

If a safe error code lacks a message property, getSafeClickhouseErrorMessage returns null, causing a StackAssertionError to be thrown instead of a user-friendly AnalyticsQueryError.

🔧 Proposed fix
   if (SAFE_CLICKHOUSE_ERROR_CODES.includes(errorCode)) {
-    return message;
+    return message ?? DEFAULT_CLICKHOUSE_ERROR_MESSAGE;
   }
apps/backend/src/lib/events.tsx (2)

198-219: Isolate ClickHouse errors to prevent disrupting PostHog logging.

The ClickHouse insert is awaited without error handling. If ClickHouse is unavailable, the entire runAsynchronouslyAndWaitUntil block fails, preventing PostHog logging from executing.

🔧 Proposed fix
     if (isClickhouseConfigured()) {
-      const clickhouseClient = getClickhouseAdminClient();
-      await clickhouseClient.insert({
-        table: "analytics_internal.events",
-        values: eventTypesArray.map(eventType => ({
-          ...
-        })),
-        format: "JSONEachRow",
-        clickhouse_settings: {
-          date_time_input_format: "best_effort",
-          async_insert: 1,
-        },
-      });
+      try {
+        const clickhouseClient = getClickhouseAdminClient();
+        await clickhouseClient.insert({
+          table: "analytics_internal.events",
+          values: eventTypesArray.map(eventType => ({
+            ...
+          })),
+          format: "JSONEachRow",
+          clickhouse_settings: {
+            date_time_input_format: "best_effort",
+            async_insert: 1,
+          },
+        });
+      } catch (error) {
+        console.error("[Events] Failed to log event to ClickHouse:", error);
+      }
     }

214-217: Add wait_for_async_insert: 1 to prevent silent data loss.

With async_insert: 1 alone, inserts are fire-and-forget - the server won't report failures back to the client. Per ClickHouse best practices, pair it with wait_for_async_insert: 1 to receive feedback on insert failures.

🔧 Proposed fix
         clickhouse_settings: {
           date_time_input_format: "best_effort",
           async_insert: 1,
+          wait_for_async_insert: 1,
         },
apps/backend/src/app/api/latest/internal/clickhouse/migrate-events/route.tsx (2)

40-41: Extract branchId from event data instead of hardcoding.

All other fields (projectId, userId, teamId, sessionId, isAnonymous) are extracted from dataRecord, but branchId is hardcoded to DEFAULT_BRANCH_ID. This causes historical events with non-default branch IDs to be migrated with incorrect data.

🔧 Proposed fix
   const projectId = typeof dataRecord.projectId === "string" ? dataRecord.projectId : "";
-  const branchId = DEFAULT_BRANCH_ID;
+  const branchId = typeof dataRecord.branchId === "string" ? dataRecord.branchId : DEFAULT_BRANCH_ID;
   const userId = typeof dataRecord.userId === "string" ? dataRecord.userId : "";

155-166: Migration is not idempotent - re-runs will create duplicate rows.

The endpoint inserts rows without checking for existing entries, and the ClickHouse table uses MergeTree (not a deduplicating engine). If migration is re-run for the same time range, duplicate rows will be inserted. Consider either:

  1. Using ReplacingMergeTree engine for the table
  2. Adding a pre-insert check to filter out existing event IDs
  3. Documenting this limitation and requiring manual table truncation before re-runs
🧹 Nitpick comments (2)
apps/backend/scripts/db-migrations.ts (1)

14-14: Unnecessary wrapper function.

The getClickhouseClient wrapper adds no value over directly calling getClickhouseAdminClient(). Consider removing it for clarity.

♻️ Suggested simplification
-const getClickhouseClient = () => getClickhouseAdminClient();

Then use getClickhouseAdminClient() directly in dropSchema.

apps/backend/src/lib/clickhouse.tsx (1)

21-39: Add JSDoc documentation reminding callers to close returned clients, or implement a singleton pattern.

getClickhouseAdminClient() and getClickhouseExternalClient() create new client instances on each call. The @clickhouse/client library maintains HTTP connection pools that require explicit cleanup via client.close(). Current usage does not invoke close() on any returned clients. Consider adding JSDoc comments to document this requirement, or refactor to use a singleton/pooling pattern for long-lived clients.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/dev-launchpad/public/index.html (1)

123-135: Fix port suffix collision on "33".

Both "ClickHouse native interface" (line 133, backgroundServices) and "Drizzle Gateway" (line 302, apps) use suffix "33". Since the port formula concatenates ${stackPortPrefix}${suffix}, both services will attempt to bind to the same port (e.g., 8133 with default prefix 81), causing a startup failure. Change one of the suffixes to an unused value.

@N2D4 N2D4 merged commit 484c3a6 into dev Jan 28, 2026
24 of 27 checks passed
@N2D4 N2D4 deleted the clickhouse-setup branch January 28, 2026 17:12
@coderabbitai coderabbitai bot mentioned this pull request Jan 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants