Skip to content

fix(telemetry)!: spell PostHog properties in snake_case - #193

Closed
thecodedrift wants to merge 2 commits into
mainfrom
fix/posthog-naming
Closed

fix(telemetry)!: spell PostHog properties in snake_case#193
thecodedrift wants to merge 2 commits into
mainfrom
fix/posthog-naming

Conversation

@thecodedrift

Copy link
Copy Markdown
Member

Spells every PostHog property in snake_case, matching PostHog's own convention.

The rule this establishes

  • PostHog properties are snake_case.
  • JSON payload fields are camelCase.

Two wire formats with two audiences: PostHog properties are read in insights and dashboards, JSON payloads are parsed by agents and scripts. Each follows its own ecosystem. The same value can legitimately appear under both spellings, and that is correct rather than something to reconcile.

The convention is now stated normatively in the analytics spec rather than being implied by whichever names happened to exist.

Renames

current becomes event
cliVersion cli_version every event + identify
scaffoldVersion scaffold_version every event + identify
durationMs duration_ms cli_run
loggedIn logged_in cli_run
ruleCount rule_count cli_rule_created, cli_rule_improved
errorCount error_count cli_check_completed
warningCount warning_count cli_check_completed

Unchanged because they are single words or already correct: cli, anonymous, command, meta, status, success, findings, gh_owner.

groupType and groupKey are parameters of the PostHog SDK's groupIdentify call, not properties we name, so they are untouched.

JSON is already compliant and is not touched. Every field across packages/cli/src/schemas/*.ts is camelCase.

The last two were nearly missed

errorCount and warningCount are built into an object in check.ts about forty lines before the capture() that sends them, so an audit that greps around capture( does not surface them. They were found by tracing the event instead.

They are folded in here rather than left for a follow-up because the cost of this rename is not the edit, it is the discontinuity, and that cost is paid once per release. Renaming five now and two later pays it twice for the same total work.

Breaking, and how

This is a hard cut with no dual-emit window. Historical events keep the old spellings permanently, so any PostHog query spanning this release needs both names to see a whole series. Saved insights, dashboards and cohorts filtering or breaking down on these seven need updating by hand; they will not error, they will silently return only the events emitted before the release.

camelCase locals are deliberate

Only the wire boundary changes. RunContext.durationMs and RunContext.loggedIn stay camelCase because they are domain types, not payloads; the translation happens where the event is emitted (duration_ms: context.durationMs). Same for the check counters. This keeps RunContext construction across the codebase and tests untouched.

Untouched for the same reason: loggedIn in taskless info --json (a JSON field, correctly camelCase), and install.cliVersion in .taskless/taskless.json (a manifest field, not telemetry).

A latent contradiction, now resolved

The cli_run requirement in the analytics spec already said cli_version while the code emitted cliVersion. Resolved in the code's favour.

Archived changes are not rewritten

openspec/changes/archive/** is left alone. Those record what actually shipped, and cliVersion genuinely was what shipped. Rewriting them would falsify the record, which is the same thing this changeset warns consumers about on the PostHog side.

Verification

pnpm typecheck, pnpm lint, pnpm test (929), pnpm cli check (exit 0), pnpm openspec validate --all --strict (24 passed).

Fixes #189

thecodedrift and others added 2 commits August 26, 2026 12:38
PostHog's own properties are `$current_url` and `$lib_version`. Ours were a
mix, which makes a property list unsearchable: you have to know which spelling
a property happened to be born with before you can filter on it.

Five renames, on the wire only: `cliVersion` -> `cli_version`,
`scaffoldVersion` -> `scaffold_version`, `durationMs` -> `duration_ms`,
`loggedIn` -> `logged_in`, `ruleCount` -> `rule_count`. `cli`, `anonymous`,
`command`, `success` are single words; `gh_owner` was already correct.
`groupIdentify`'s `groupType`/`groupKey` are the SDK's parameter names, not
properties we choose, and are untouched.

The TypeScript feeding those properties stays camelCase. `RunContext` is a
domain type, not a wire format, so `duration_ms: context.durationMs` is the
translation happening at the one boundary where it belongs rather than
camelCase leaking outward or snake_case leaking inward.

`taskless info --json` keeps its `loggedIn` field. Two wire formats with two
audiences, each following its own ecosystem's convention, so the same value
appearing as `loggedIn` in JSON and `logged_in` in telemetry is correct rather
than a discrepancy to reconcile. The analytics spec now says so normatively, so
the next property added does not have to rediscover it.

Spec and tests move with the code. The `analytics` spec named `cliVersion` and
`scaffoldVersion` in the standard-properties requirement and four scenarios,
and `durationMs`/`loggedIn` in the cli_run requirement; leaving any of them
would have left the spec asserting a name nothing emits. Its cli_run
requirement already said `cli_version`, which is the inconsistency this fixes,
now true rather than aspirational.

Hard cut, no dual-emit. Historical events keep the old spellings permanently,
so a query spanning the release needs both, and saved insights filtering on
these five need updating by hand.

Fixes #189

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`cli_check_completed` carries `errorCount` and `warningCount`, built into an
object forty lines before the `capture()` that sends them, which is why the
audit behind the rename table missed both. The analytics spec named them as
well.

Folded into this change rather than left for a follow-up. The cost of the
rename is not the edit, it is the historical discontinuity: events emitted
before the release keep the old spelling forever, and saved insights need
updating by hand. That cost is paid once per release. Renaming five now and two
later pays it twice for the same total work, and this branch already carries the
changeset and the dashboard-migration note that make it payable once.

The locals stay camelCase. They are ordinary TypeScript, not a payload, and the
translation happens at the wire boundary, matching how `duration_ms:
context.durationMs` reads in `telemetry-run.ts`.
@thecodedrift

Copy link
Copy Markdown
Member Author

Closing. The premise behind this PR is wrong, and the evidence I collected supported the opposite conclusion.

The codebase was consistent, not inconsistent. Measured on main at 3c78ffe:

convention
event names cli_run, cli_check_completed, cli_rule_created, and 7 more, all snake_case
properties cli, cliVersion, scaffoldVersion, durationMs, loggedIn, ruleCount, errorCount, warningCount, all camelCase

That is a deliberate split: snake_case for the event taxonomy, camelCase for properties. Every property followed it.

gh_owner is the only exception, and I introduced it in #190. When the property was renamed from gh_org to gh_owner I carried the underscore across without checking what its neighbours looked like. Then I audited, found one snake_case property beside seven camelCase ones, and concluded the seven were wrong.

The justification I wrote here does not survive either. PostHog spells $current_url and $lib_version that way because they are its own reserved properties; that says nothing about what a project should call its own.

So this PR renames seven properties away from the house convention, pays a permanent discontinuity in event history to do it, and adds a normative spec requirement asserting the wrong rule.

The minimal correct change is the inverse: rename gh_owner to ghOwner. One property, no historical discontinuity, no dashboards to update. Opening that separately.

Two things worth carrying forward from here rather than losing:

  • The convention should be written down. This PR added a normative requirement to the analytics spec, which was the right instinct with the wrong content. The replacement states the real rule: snake_case event names, camelCase properties.
  • The changeset bump was wrong too. It said patch while both commits were marked !, and comparable behaviour changes in this repo use minor. Moot now, but worth not repeating.

Correction recorded on #189.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Standardize property naming: snake_case for PostHog, camelCase for JSON

1 participant