clickhouse-migrations — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited clickhouse-migrations (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Read posthog/clickhouse/migrations/AGENTS.md for comprehensive patterns, cluster setup, examples, and ingestion layer details.
operations = [
run_sql_with_exceptions(
SQL_FUNCTION(),
node_roles=[...],
sharded=False, # True for sharded tables
is_alter_on_replicated_table=False # True for ALTER on replicated tables
),
][NodeRole.DATA]: Sharded tables (data nodes only)[NodeRole.DATA, NodeRole.COORDINATOR]: Non-sharded data tables, distributed read tables, replicated tables, views, dictionaries[NodeRole.INGESTION_SMALL]: Writable tables, Kafka tables, materialized views on ingestion layerMergeTree engines:
AggregatingMergeTree(table, replication_scheme=ReplicationScheme.SHARDED) for sharded tablesReplacingMergeTree(table, replication_scheme=ReplicationScheme.REPLICATED) for non-shardedCollapsingMergeTree, ReplacingMergeTreeDeletedDistributed engine:
Distributed(data_table="sharded_events", sharding_key="sipHash64(person_id)")Distributed(data_table="my_table", cluster=settings.CLICKHOUSE_SINGLE_SHARD_CLUSTER)ON CLUSTER clause in SQL statementsIF EXISTS / IF NOT EXISTS clausesDROP TABLE IF EXISTS ... SYNCon_cluster=Falsesharded=True when altering sharded tablesis_alter_on_replicated_table=True when altering non-sharded replicated tablesDROP COLUMN can get stuck in ClickHouse and block releases. Column removal is a two-step process: (1) the ClickHouse team drops the column directly on the cluster, then (2) you write a migration with the matching DROP COLUMN so the codebase schema stays in sync. Never initiate the drop from a migration without the ClickHouse team having done step 1 first.mat_* columns) and those differences are not reflected in the repo. Dropping and recreating from repo SQL would destroy the environment-specific schema and break event ingestion. Any change must go through the ClickHouse team.A PR that contains a ClickHouse migration must be migration-only. Do not mix migration files with feature code, API changes, model changes, or frontend changes in the same PR. Migration-related files are:
posthog/clickhouse/migrations/0NNN_*.py)posthog/clickhouse/sql/*.py, table engine helpers)If you need both a schema change and application code that uses the new schema, ship the migration first in its own PR and merge it before the application-code PR.
No table should exist only in the cloud. Every table created via migration must also exist in a local dev environment.
Some migrations are cloud-guarded and skipped in local/hobby dev:
operations = (
[]
if settings.CLOUD_DEPLOYMENT not in ("US", "EU", "DEV")
else [...]
)If you create a new table inside such a guard, you must also add its SQL function to posthog/clickhouse/schema.py in the appropriate tuple so the table gets created locally:
| Table type | Tuple in schema.py |
|---|---|
| MergeTree / base table | CREATE_MERGETREE_TABLE_QUERIES |
| Distributed / writable | CREATE_DISTRIBUTED_TABLE_QUERIES |
| Kafka consumer | CREATE_KAFKA_TABLE_QUERIES |
| Materialized view | CREATE_MV_TABLE_QUERIES |
| Non-materialized view | CREATE_VIEW_QUERIES |
| Dictionary | CREATE_DICTIONARY_QUERIES |
The only exception is tables whose definition intentionally differs per environment and is not tracked in the repo (e.g. the no-go zone events_json_ws_mv table).
Dictionary credentials: when a dictionary uses a SOURCE(CLICKHOUSE(...)), resolve the source user/password via get_clickhouse_creds(ClickHouseUser.DICT_READER) and interpolate them into the USER/PASSWORD clause — do not hardcode default/CLICKHOUSE_USER or omit credentials. This keeps dictionary auth on the dedicated low-privilege dict_reader user, decoupled from default; it falls back to default creds when the env vars are unset. See posthog/models/exchange_rate/sql.py for the pattern.
Delete entry from infi_clickhouse_orm_migrations table to re-run a migration.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.