Skip to content

feat(clickhouse): switch schema setup and retire legacy execution - #111081

Draft
rorylshanks wants to merge 2 commits into
codex/clickhouse-schema-canary-changefrom
codex/clickhouse-schema-cutover
Draft

rorylshanks wants to merge 2 commits into
codex/clickhouse-schema-canary-changefrom
codex/clickhouse-schema-cutover

Conversation

@rorylshanks

@rorylshanks rorylshanks commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Problem

Operators and test runners use the declarative schema after cloud ownership has been verified.

Changes

Routes local migration entrypoints and test setup through OpenTofu; disables automatic legacy HCL and multinode workflows. Dormant migrations and helpers remain for separate cleanup.

Rollout gate: Merge after the canary succeeds and infra ownership https://github.com/PostHog/posthog-cloud-infra/pull/10907 has adopted every root and node without DDL. Observe this layer before cleanup. Merge bottom-first; do not land the whole stack together.

Before:

flowchart LR
  A["Python migrations"] --> B["Local and test schema"]
  classDef phBlue fill:#1d4aff,stroke:#1d4aff,color:#fff;
  classDef phGray fill:#e5e7eb,stroke:#c7ccd1,color:#000;
  class A phBlue;
  class B phGray;
Loading

After:

flowchart LR
  A["OpenTofu"] --> B["Local and test schema"]
  classDef phBlue fill:#1d4aff,stroke:#1d4aff,color:#fff;
  classDef phGray fill:#e5e7eb,stroke:#c7ccd1,color:#000;
  class A phBlue;
  class B phGray;
Loading

How did you test this code?

Local catalogue validation and table-family checks passed, including the empty second plan. posthog/clickhouse/test/test_managed_schema.py --reuse-db passed against local services. Python lint/format, lockfile and workflow checks passed. GitHub script tests passed with GNU sed, matching Linux CI; the pinned schema formatter passed. Live cloud rollout, the complete backend suite and both events-schema variants remain CI/review gates.

Test rationale: The imported managed-schema regression checks reconstruction after fixture restore. Existing table-family checks validate the catalogue and require an empty second plan; no split-only tests were added.

Release status

  • No feature flag controls this change
  • This change is behind a feature flag and is not available to users
  • This change makes a previously flagged feature available to everyone

Automatic notifications

  • Publish to changelog?

Docs update

Updated the existing schema README and applicable schema-change guidance to describe this rollout stage.

🤖 Agent context

Autonomy: Human-driven (agent-assisted). Agent: Codex, GPT-6.

Staged replacement for #109937, which remains available for comparison. Source changes were split using Git, GitHub CLI and gh stack; current master changes were retained. Skills: /stacking-prs, /writing-pr-descriptions, /running-ci-preflight, /authoring-ci-workflows, /clickhouse-migrations, /writing-tests, /writing-code-comments and /reviewing-with-coderabbit. Local CodeRabbit review was skipped at the directing user's request because the CLI was missing. No customer material was used. Human review and CI patch coverage are pending on this draft.

@rorylshanks rorylshanks added the test-new-events-schema Run the backend CI matrices twice: legacy events schema and native JSON events schema label Oct 2, 2026
@rorylshanks rorylshanks self-assigned this Oct 2, 2026
@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

🤖 CI report

🚨 Trunk lane — universal lane

This PR is assigned to the universal lane. It cannot merge in parallel with other PRs, so it can take longer to merge. Ask dev-ex if you think this is wrong.

⚠️ Duplication (Python) — 4 new duplicated blocks (worst 141 tokens)

New Python code duplication introduced by this branch. Fails at 70+ tokens in app code, or 150+ tokens when both copies live in test files. Advisory while the gate proves itself: extract a shared helper instead of copying.

First copy Second copy Lines Tokens
products/warehouse_sources/backend/temporal/data_imports/sources/aws_compute_optimizer/source.py:20 products/warehouse_sources/backend/temporal/data_imports/sources/aws_step_functions/source.py:17 16 141
products/warehouse_sources/backend/temporal/data_imports/sources/aws_sagemaker/source.py:113 products/warehouse_sources/backend/temporal/data_imports/sources/aws_step_functions/source.py:107 29 107
products/warehouse_sources/backend/temporal/data_imports/sources/aws_compute_optimizer/aws_compute_optimizer.py:17 products/warehouse_sources/backend/temporal/data_imports/sources/aws_step_functions/aws_step_functions.py:16 11 99
posthog/hogql/property.py:977 products/endpoints/backend/models.py:36 12 73
✅ Duplication (TypeScript) — clean

New TypeScript code duplication introduced by this branch. Fails at 70+ tokens in app code, or 150+ tokens when both copies live in test files. Advisory while the gate proves itself: extract a shared helper instead of copying.

🚨 Comment density — 9% of added code lines are comments (22 of 233)

This section warns when comments are more than 3% of the code lines a PR adds, and alerts above 6%. Before agent-assisted PRs, the typical share was about 2%. Only full-line comments count. Docstrings, generated files, snapshots, migrations, and workflow files are left out.

Comments that restate the code, record how the change came about, or narrate the next line add noise for the next reader. Keep the comments that explain a reason the code cannot show, and remove the rest. See .agents/skills/writing-code-comments/SKILL.md for the house rules.

Files with the most added comment lines:

File Comment lines Added lines
posthog/clickhouse/managed_schema.py 13 151
posthog/conftest.py 4 23
posthog/management/commands/migrate_clickhouse.py 4 30
posthog/test/base.py 1 8

This check does not block merging. It updates on every push and clears when the share drops.

✅ Bundle size — no change

Uncompressed size of every built .js bundle, compared against the base branch.

Total: 69.79 MiB · no change

No file changed by more than 1000 B.

Posted automatically by build-bundle-size-report · uncompressed bytes from dist-report

✅ Eager graph — within budget

How much code each root ships on the eager path — downloaded and parsed before the surface is interactive. Measured from the esbuild output chunks (post-tree-shake, static imports only); lazy import() / React.lazy chunks are not counted.

Root Eager (shipped) Δ vs base Budget
entry (logged-out pages, app bootstrap)
src/index.tsx
1.63 MiB · 22 files no change █████████░ 88.7% of 1.84 MiB
logged-out boot: index + App + bootApp (preloaded by every page, including /login)
src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
3.72 MiB · 661 files no change █████████░ 92.3% of 4.03 MiB
authenticated shell (every logged-in page)
src/scenes/AuthenticatedShell.tsx
7.59 MiB · 2,409 files no change █████████░ 91.0% of 8.34 MiB
dashboard scene
src/scenes/dashboard/Dashboard.tsx
9.67 MiB · 3,392 files no change ███████░░░ 71.8% of 13.48 MiB
today home path
src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
7.61 MiB · 2,417 files no change █████████░ 88.6% of 8.58 MiB
events scene
src/scenes/activity/explore/EventsScene.tsx
9.29 MiB · 3,244 files no change ███████░░░ 73.5% of 12.64 MiB
replay detail scene
src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
12.12 MiB · 4,130 files no change ████████░░ 77.1% of 15.72 MiB

🟢 node_modules/monaco-editor/ stays out of src/index.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 node_modules/monaco-editor/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/layout/navigation-3000/navigationLogic.tsx stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/scenes/dashboard/dashboardLogic.tsx stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/lib/lemon-ui/LemonMarkdown/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/lib/components/RichContentEditor/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/lib/components/CodeSnippet/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/taxonomy/core-filter-definitions-by-group.json stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 node_modules/monaco-editor/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/scenes/session-recordings/player/sessionRecordingPlayerLogic.ts stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
🟢 src/scenes/project-homepage/today/TodayReportPage.tsx stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx

Largest files eagerly shipped from src/index.tsx
Size File
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
24.6 KiB ../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.js
6.3 KiB ../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js
4.5 KiB ../node_modules/.pnpm/@jspm+core@2.1.0/node_modules/@jspm/core/nodelibs/browser/process.js
3.9 KiB ../node_modules/.pnpm/scheduler@0.23.2/node_modules/scheduler/cjs/scheduler.production.min.js
1.4 KiB ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js
1.3 KiB src/index.tsx
1.3 KiB src/RootErrorBoundary.tsx
912 B ../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js
854 B src/scenes/ChunkLoadErrorBoundary.tsx
Largest files eagerly shipped from src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
Size File
306.2 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
220.3 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
100.5 KiB src/lib/api.ts
92.7 KiB src/products.tsx
69.4 KiB src/lib/lemon-ui/icons/icons.tsx
40.1 KiB src/lib/utils/eventUsageLogic.ts
38.7 KiB ../node_modules/.pnpm/@dnd-kit+core@6.0.8_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@dnd-kit/core/dist/core.esm.js
33.9 KiB ../node_modules/.pnpm/kea@4.0.0-pre.6_patch_hash=139b8d1f1304f9d9da452a9a1244c94ea679dbcb85687d8999563146879fb6f5_react@18.3.1/node_modules/kea/lib/index.cjs.js
29.0 KiB ../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/schemas.js
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
Size File
306.2 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
279.9 KiB src/taxonomy/core-filter-definitions-by-group.json
220.3 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
153.7 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
110.1 KiB ../packages/quill/packages/quill/dist/index.js
100.5 KiB src/lib/api.ts
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
92.7 KiB src/products.tsx
90.6 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js
Largest files eagerly shipped from src/scenes/dashboard/Dashboard.tsx
Size File
306.2 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
279.9 KiB src/taxonomy/core-filter-definitions-by-group.json
220.3 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
181.8 KiB src/queries/validators.js
153.7 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
110.1 KiB ../packages/quill/packages/quill/dist/index.js
100.5 KiB src/lib/api.ts
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
92.7 KiB src/products.tsx
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
Size File
306.2 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
279.9 KiB src/taxonomy/core-filter-definitions-by-group.json
220.3 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
153.7 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
110.1 KiB ../packages/quill/packages/quill/dist/index.js
100.5 KiB src/lib/api.ts
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
92.7 KiB src/products.tsx
90.6 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js
Largest files eagerly shipped from src/scenes/activity/explore/EventsScene.tsx
Size File
306.2 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
279.9 KiB src/taxonomy/core-filter-definitions-by-group.json
220.3 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
181.8 KiB src/queries/validators.js
153.7 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
110.1 KiB ../packages/quill/packages/quill/dist/index.js
100.5 KiB src/lib/api.ts
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
92.7 KiB src/products.tsx
Largest files eagerly shipped from src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
Size File
315.5 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/rrweb.js
306.2 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
279.9 KiB src/taxonomy/core-filter-definitions-by-group.json
220.3 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
181.8 KiB src/queries/validators.js
153.7 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
110.1 KiB ../packages/quill/packages/quill/dist/index.js
100.5 KiB src/lib/api.ts
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js

Posted automatically by check-eager-graph · sizes are eager output bytes (shipped, post-tree-shake) from the esbuild metafile · part of #32479

✅ Toolbar bundle — eager 2.20 MiB within budget

What the toolbar ships to customer pages, measured from the esbuild output (minified, post-tree-shake). The eager set is the entry plus everything statically imported from it — fetched before any feature runs; deferred chunks load lazily. The eager guardrail is 5.72 MiB. Each output file must also stay below 10 MB, where CloudFront stops compressing it. The module boundary is enforced separately by check-toolbar-graph.

Metric Size Δ vs base Budget
Eager (shipped)
entry + static imports
2.20 MiB · 19 files no change ████░░░░░░ 38.4% of 5.72 MiB
Deferred (lazy) 2.11 MiB · 44 files no change n/a — loads on demand
Loader dist/toolbar.js 1.2 KiB no change █░░░░░░░░░ 6.0% of 19.5 KiB
Largest eagerly-shipped chunks
Size File
835.5 KiB dist/toolbar/toolbar-app-FSDWO46I.css
657.5 KiB dist/toolbar/chunk-chunk-BALLB66W.js
259.4 KiB dist/toolbar/chunk-chunk-7JWMBALG.js
138.2 KiB dist/toolbar/chunk-chunk-RXJG6CZC.js
131.8 KiB dist/toolbar/chunk-chunk-FDH2IBXT.js
75.2 KiB dist/toolbar/toolbar-app-YIYWW6XJ.js
69.0 KiB dist/toolbar/chunk-chunk-TSAL54PB.js
35.6 KiB dist/toolbar/chunk-chunk-MM7MZI2L.js
21.0 KiB dist/toolbar/chunk-chunk-EZFR5QGQ.js
6.8 KiB dist/toolbar/chunk-chunk-DV7IWQNF.js

Posted automatically by check-toolbar-size · sizes are toolbar output bytes (shipped, post-tree-shake) from the esbuild metafile

✅ Dist folder size — no change

Total size of the built frontend/dist folder (all assets), compared against the base branch.

Total: 960.35 MiB · no change

@rorylshanks
rorylshanks added this pull request to stack #111084 October 2, 2026 18:59
@greptile-apps

greptile-apps Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Retrigger

[Critical risk] Switches ClickHouse schema setup from Python to OpenTofu tooling.

The PR is not safe to merge until test resets preserve declared reference data and the standalone reset command works.

Reviews (1) · Last reviewed commit: "feat(clickhouse): switch schema setup an..."

Comment thread posthog/conftest.py
FROM system.tables
WHERE database = '{settings.CLICKHOUSE_DATABASE}' AND name LIKE 'kafka_%'
WHERE database = %(database)s
AND ((engine LIKE '%%MergeTree' AND total_rows > 0) OR (engine = 'Kafka' AND %(drop_kafka)s))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Channel definitions are erased With --reuse-db, package teardown truncates the nonempty channel_definition table, but seed() reloads only exchange_rate. The next package starts with an empty source for the channel-definition dictionary, changing channel classification results. Exclude schema-declared reference tables from this reset or restore their rows afterward.

Prompt To Fix With AI
This is a comment left during a code review.
Path: posthog/conftest.py
Line: 65

Comment:
**Channel definitions are erased** With `--reuse-db`, package teardown truncates the nonempty `channel_definition` table, but `seed()` reloads only `exchange_rate`. The next package starts with an empty source for the channel-definition dictionary, changing channel classification results. Exclude schema-declared reference tables from this reset or restore their rows afterward.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment thread posthog/test/base.py
),
]
)
ClickHouseDatabase().restore()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Standalone reset cannot start On a fresh invocation, reset_test_clickhouse_db calls restore() before this process has captured a schema snapshot. restore() raises, so the command cannot reset the test database. Build or capture the schema first, or give the command a reset path that does not require an in-process snapshot.

Prompt To Fix With AI
This is a comment left during a code review.
Path: posthog/test/base.py
Line: 1912

Comment:
**Standalone reset cannot start** On a fresh invocation, `reset_test_clickhouse_db` calls `restore()` before this process has captured a schema snapshot. `restore()` raises, so the command cannot reset the test database. Build or capture the schema first, or give the command a reset path that does not require an in-process snapshot.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines +60 to +63
database.create()
database.apply_schema(kafka=True)
database.seed()
self.stdout.write(self.style.SUCCESS("ClickHouse schema is up to date"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Broken legacy method remains This new handle() path never calls migrate(), but that method remains and expects upto, fake, and print_sql options that the parser no longer supplies. Remove the unreachable method as part of retiring legacy execution; leaving it in place obscures the supported path and gives direct callers a broken method.

Prompt To Fix With AI
This is a comment left during a code review.
Path: posthog/management/commands/migrate_clickhouse.py
Line: 60-63

Comment:
**Broken legacy method remains** This new `handle()` path never calls `migrate()`, but that method remains and expects `upto`, `fake`, and `print_sql` options that the parser no longer supplies. Remove the unreachable method as part of retiring legacy execution; leaving it in place obscures the supported path and gives direct callers a broken method.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.


The OpenTofu catalogue in `posthog/clickhouse/schema/` is being introduced alongside Python migrations. During the foundation stage, normal migration, local setup and test commands still use Python. Apply the new catalogue only to an isolated database or an explicitly adopted infrastructure canary. Ownership cutover and removal of the legacy tooling happen in separate pull requests.

The ownership cutover switches local, test and Hobby setup to OpenTofu. Cloud deployment hooks stop applying ClickHouse schema; infrastructure owns it. Merge the cutover only after the canary migration succeeds and every cloud object has a verified owner. Legacy definitions remain dormant until cleanup.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Setup instructions contradict each other The preceding paragraph says normal local and test setup still uses Python migrations and limits OpenTofu to isolated databases; this paragraph says those paths now use OpenTofu. Replace the outdated foundation-stage instructions so operators have one accurate setup procedure.

Prompt To Fix With AI
This is a comment left during a code review.
Path: docs/published/handbook/engineering/databases/schema-changes.md
Line: 54

Comment:
**Setup instructions contradict each other** The preceding paragraph says normal local and test setup still uses Python migrations and limits OpenTofu to isolated databases; this paragraph says those paths now use OpenTofu. Replace the outdated foundation-stage instructions so operators have one accurate setup procedure.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@coderabbitai

coderabbitai Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

📝 Walkthrough

Walkthrough

ClickHouse schema application and planning now use a managed database wrapper. Runtime migration and test setup paths use the wrapper for database creation, schema operations, restoration, and seeding. The container installs the schema tools. Rollout documentation and ClickHouse configuration are updated. CI path filters include schema tooling, while automated migration checks and selected workflow triggers are removed.

🚥 Pre-merge checks | ✅ 1
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The description covers the problem, user-visible changes, rollout gate, testing, test rationale, release status, documentation, and agent context. It includes the required before-and-after flowcharts …
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

🟡 Other comments (3)
posthog/clickhouse/managed_schema.py-82-87 (1)

82-87: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Add a timeout to the schema tool subprocess.

subprocess.run has no timeout. If bin/clickhouse-schema hangs, for example on a provider download, a Keeper wait, or a stuck ClickHouse connection, then migrate_clickhouse, test setup, and deploy hooks block with no limit. Set a bounded timeout. Convert subprocess.TimeoutExpired into the same RuntimeError path.

Proposed fix
         return subprocess.run(
             [str(REPO_ROOT / "bin" / "clickhouse-schema"), *args, "-no-color"],
             env=env,
             capture_output=True,
             text=True,
+            timeout=1800,
         )

Based on learnings: "flag missing timeout arguments... Recommend a sensible default (e.g., 300s) and handle subprocess.TimeoutExpired."

Source: Learnings

posthog/clickhouse/test/test_managed_schema.py-11-15 (1)

11-15: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert that the dropped table comes back.

The test drops trace_spans, but it checks only exchange_rate. If the rebuild does not recreate trace_spans, the test still passes. Assert that trace_spans exists.

Proposed fix
     assert sync_execute("SELECT count() FROM exchange_rate")[0][0] > 0
+    assert sync_execute(
+        "SELECT count() FROM system.tables WHERE database = currentDatabase() AND name = 'trace_spans'"
+    )[0][0] == 1
posthog/clickhouse/managed_schema.py-117-119 (1)

117-119: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

declares_column returns false for an empty snapshot.

_snapshot is populated only by create_test_tables. If cleanup_materialized_columns runs in a process that has not called snapshot(), declares_column returns False for every column. posthog/test/base.py then drops columns that the schema declares. Raise an error when _snapshot is empty instead of returning False.

🧹 Nitpick comments (1)
posthog/management/commands/migrate_clickhouse.py (1)

7-17: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the dead legacy migrate path.

handle no longer calls migrate, get_migrations, or the _create_* helpers. These methods still read options["upto"], options["fake"], and options["print_sql"], and this change removes those arguments. Any future call to them raises KeyError. They also keep the infi imports alive. Delete them in this PR, or state that the cleanup PR deletes them.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: PostHog/posthog/.coderabbit.yaml

Review profile: QUIET

Plan: Enterprise

Run ID: d028a590-6983-4698-bfaf-b072ba7adcfb

📥 Commits

Reviewing files that changed from the base of the PR and between 4dcb77a and 46ede39.

📒 Files selected for processing (25)
  • .depot/workflows/ci-backend.yml
  • .github/scripts/test_ci_migration_selection.py
  • .github/workflows/ci-backend.yml
  • .github/workflows/ci-clickhouse-hcl-schema.yml
  • .github/workflows/ci-clickhouse-multinode-migrations.yml
  • .github/workflows/ci-dagster.yml
  • .github/workflows/ci-mcp.yml
  • .github/workflows/ci-nodejs.yml
  • .github/workflows/ci-rust.yml
  • Dockerfile
  • bin/migrate
  • bin/migrate-check
  • docker-compose.dev.yml
  • docker/clickhouse/config.d/default.xml
  • docker/kafka/topics.txt
  • docs/published/handbook/engineering/databases/schema-changes.md
  • posthog/clickhouse/managed_schema.py
  • posthog/clickhouse/schema/README.md
  • posthog/clickhouse/test/test_managed_schema.py
  • posthog/conftest.py
  • posthog/hogql/cost/test/test_statistics.py
  • posthog/management/commands/migrate_clickhouse.py
  • posthog/management/commands/setup_test_environment.py
  • posthog/test/base.py
  • products/batch_exports/backend/tests/temporal/conftest.py
💤 Files with no reviewable changes (5)
  • .github/workflows/ci-clickhouse-multinode-migrations.yml
  • bin/migrate-check
  • .github/workflows/ci-clickhouse-hcl-schema.yml
  • .github/scripts/test_ci_migration_selection.py
  • bin/migrate

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 10 remain after this review.

@rorylshanks
rorylshanks force-pushed the codex/clickhouse-schema-cutover branch from 46ede39 to eb14e80 Compare October 2, 2026 19:17
@trunk-io

trunk-io Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Static Badge   Static Badge   Static Badge

View Full Report ↗︎ ⋅ Docs

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test-new-events-schema Run the backend CI matrices twice: legacy events schema and native JSON events schema

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant