-
Notifications
You must be signed in to change notification settings - Fork 498
clickhouse setup #1032
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
clickhouse setup #1032
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughAdds 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
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
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
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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. Comment |
Greptile OverviewGreptile SummaryThis PR establishes the foundational ClickHouse integration for analytics, adding database infrastructure, client library, API endpoint, and comprehensive security controls. Major Changes:
Issues Found:
Confidence Score: 2/5
Important Files ChangedFile Analysis
Sequence DiagramsequenceDiagram
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
|
There was a problem hiding this 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
There was a problem hiding this 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_passwordis acceptable for development environments, consider documenting this choice or usingsha256_passwordfor 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
clientaccessType is rejected, since the route requiresserverOrHigherAuthTypeSchema.
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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis 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 passespnpm typecheckfor TypeScript type validation
Never usetoastfor 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; userunAsynchronouslyorrunAsynchronouslyWithAlertfor 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.tsapps/e2e/tests/backend/endpoints/api/v1/analytics-query.test.tsapps/e2e/tests/snapshot-serializer.tsapps/backend/src/app/api/latest/analytics/query/route.tsxapps/backend/src/lib/clickhouse.tsxpackages/stack-shared/src/known-errors.tsxapps/backend/scripts/db-migrations.ts
apps/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Prefer client components and dynamic functions like
usePathnameover Next.js dynamic functions to keep pages static; avoid usingawait paramsor similar dynamic patterns
Files:
apps/backend/scripts/clickhouse-migrations.tsapps/e2e/tests/backend/endpoints/api/v1/analytics-query.test.tsapps/e2e/tests/snapshot-serializer.tsapps/backend/src/app/api/latest/analytics/query/route.tsxapps/backend/src/lib/clickhouse.tsxapps/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.tsapps/e2e/tests/snapshot-serializer.ts
**/*.{test,spec}.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{test,spec}.{ts,tsx}: Run all tests usingpnpm test run; run specific tests usingpnpm test run <file-filters>with Vitest
Use.toMatchInlineSnapshotfor 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.tsapps/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
stripFieldsis 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
.envfile.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.0specifier 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
AnalyticsQueryTimeoutandAnalyticsQueryErrorconstructors follow the established pattern for known errors. The HTTP 400 status is appropriate, and theconstructorArgsFromJsonimplementations correctly deserialize the error details.
1784-1785: LGTM!The new error types are properly exported in the
KnownErrorsobject.
There was a problem hiding this 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 userThis script still has the two issues called out in earlier reviews:
GRANT SELECT ON analytics.allowed_table1 TO limited_user;will fail ifanalytics.allowed_table1doesn’t exist yet (which seems likely given the TODO about creating migration files).- The external user name is hardcoded as
limited_user, even thoughSTACK_CLICKHOUSE_EXTERNAL_USERexists 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: FixgetQueryTimingStatstyping and guard against missing query_log rowsThis function still has the issues called out in earlier reviews:
profile.jsonis typed as if it returns a flat{ cpu_time_ms, wall_clock_time_ms }, but you then accessstats.data[0]; this doesn’t match the ClickHouse JSON shape and will likely fail type-checking.- At runtime,
system.query_logcan legitimately return an emptydataarray (log entry not flushed yet), makingstats.data[0]undefinedand 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.AnalyticsQueryErroror 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 ClickHouseThe 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
sleepwith 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 inpackages/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 -->
There was a problem hiding this 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
📒 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:8133tohost.docker.internal:8133ensures 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.
There was a problem hiding this 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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis 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 usetoast, 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). UserunAsynchronouslyorrunAsynchronouslyWithAlertinstead 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.
Older cmux preview screenshots (latest comment is below)Preview ScreenshotsOpen Workspace (1 hr expiry) · Open Dev Browser (1 hr expiry) · Open Diff Heatmap Captured 3 screenshots for commit Element screenshot of the new ClickHouse service tile at port 8133 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 Element screenshot of the new MCPJam Inspector service tile at port 8126 Generated by cmux preview system |
There was a problem hiding this 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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis 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
AnalyticsQueryTimeoutandAnalyticsQueryErrorerror constructors are well-implemented:
- Follow the existing
createKnownErrorConstructorpattern consistently- Use appropriate 400 status codes for client errors
- Include descriptive messages and properly structured details objects
- Correctly implement
constructorArgsFromJsonfor deserialization- Properly exported in the
KnownErrorscollectionAlso applies to: 1788-1789
There was a problem hiding this 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_table1doesn'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 wrappinggetQueryTimingStatserrors.If
getQueryTimingStatsfails (e.g., query log entry not found), it throws aStackAssertionErrorwhich will propagate as an internal error rather than a user-friendlyAnalyticsQueryError. 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 versionsThe
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_ERRORdue 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
📒 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.tsapps/e2e/tests/backend/endpoints/api/v1/analytics-query.test.tsapps/backend/scripts/clickhouse-migrations.tsapps/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.tsapps/e2e/tests/backend/endpoints/api/v1/analytics-query.test.tsapps/backend/scripts/clickhouse-migrations.tsapps/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.tsapps/e2e/tests/backend/endpoints/api/v1/analytics-query.test.tsapps/backend/scripts/clickhouse-migrations.tsapps/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.tsapps/e2e/tests/backend/endpoints/api/v1/analytics-query.test.tsapps/backend/scripts/clickhouse-migrations.tsapps/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.tsapps/backend/scripts/clickhouse-migrations.tsapps/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
clickhouseExternalClientsingleton 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 useSTACK_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
StackAssertionErrorwith 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.
Preview Screenshots⏳ Preview screenshots are being captured... Workspace and dev browser links will appear here once the preview environment is ready. Generated by cmux preview system |
There was a problem hiding this 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/clickhouseand used elsewhere in the codebase, e.g.,apps/backend/src/lib/events.tsxline 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')andgetSetting('SQL_branch_id'), but there's no configuration to:
- Allow these custom settings to be changed in readonly mode
- Create a role with
CHANGEABLE_IN_READONLYconstraints for these settings- Verify that server-side
custom_settings_prefixesallows theSQL_prefixWithout this configuration, the
limited_userwon'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
📒 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 PRIVILEGESfollowed byREVOKE ALL) ensures a clean slate, and the grant ondefault.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_atfor 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:
- Creates
analytics_internaldatabase first- Creates
analytics_internal.eventstable- Creates
default.eventsview pointing to the table- 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.
There was a problem hiding this 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: AvoidteamId: ""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 ?? nullis incompatible with the current schema. TheSessionActivityEventTypeschema definesteamIdasyupString().optional().default(""), which does not acceptnullas a valid type—only strings orundefined.Two better approaches:
Use
undefinedto trigger the schema default (minimal change):teamId: user.selected_team_id || undefined,This lets the schema apply its intended default.
Update the schema to support
null(semantic improvement):
- Change the schema to
yupString().nullable().default(null)- Update the extraction logic in
logEventto handle null as a distinct value from empty string- Then use
teamId: user.selected_team_id ?? nullOption 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
aBcdmatch, 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: ExtractbranchIdfrom event data instead of hardcoding.This hardcodes
branchIdtoDEFAULT_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 preferinterfaceovertype.
createClickhouseClientis imported but never used in this file. Also, per coding guidelines, preferinterfacefor 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 theanytype usage.Per coding guidelines, when using
any, leave a comment explaining why. Thedatafield 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. TheprogressCursorCreatedAt!assertions on lines 182-183 rely on the outerprogressCursorcheck, 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;
apps/backend/src/app/api/latest/internal/clickhouse/migrate-events/route.tsx
Show resolved
Hide resolved
There was a problem hiding this 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
runClickhouseMigrationswhich 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
nullwhich triggers aStackAssertionError. 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 callingclient.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
messageproperty,getSafeClickhouseErrorMessagereturnsnull, causing aStackAssertionErrorto be thrown instead of a user-friendlyAnalyticsQueryError.🔧 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
runAsynchronouslyAndWaitUntilblock 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: Addwait_for_async_insert: 1to prevent silent data loss.With
async_insert: 1alone, inserts are fire-and-forget - the server won't report failures back to the client. Per ClickHouse best practices, pair it withwait_for_async_insert: 1to 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, butbranchIdis hardcoded toDEFAULT_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:
- Using
ReplacingMergeTreeengine for the table- Adding a pre-insert check to filter out existing event IDs
- 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
getClickhouseClientwrapper adds no value over directly callinggetClickhouseAdminClient(). Consider removing it for clarity.♻️ Suggested simplification
-const getClickhouseClient = () => getClickhouseAdminClient();Then use
getClickhouseAdminClient()directly indropSchema.apps/backend/src/lib/clickhouse.tsx (1)
21-39: Add JSDoc documentation reminding callers to close returned clients, or implement a singleton pattern.
getClickhouseAdminClient()andgetClickhouseExternalClient()create new client instances on each call. The@clickhouse/clientlibrary maintains HTTP connection pools that require explicit cleanup viaclient.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.
apps/backend/src/app/api/latest/internal/clickhouse/migrate-events/route.tsx
Show resolved
Hide resolved
apps/backend/src/app/api/latest/internal/clickhouse/migrate-events/route.tsx
Show resolved
Hide resolved
There was a problem hiding this 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.
Summary by CodeRabbit
New Features
Dashboard
Infrastructure
Tests
Docs / Errors
✏️ 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.
@/lib/clickhouseclient, env vars, and migrations creatinganalytics.events; row policy and restrictedlimited_usergrants.analytics.events(src/lib/events.tsx).POST /api/v1/internal/analytics/queryexecuting readonly, tenant-scoped ClickHouse queries and returning results + CPU/wall times; addsKnownErrorsfor analytics.queryAnalytics, ClickHouse migration) and analytics types..envexamples andclickhousescripts; Dev Launchpad entries.Written by Cursor Bugbot for commit 1134883. This will update automatically on new commits. Configure here.