default_team_id is an integer end-to-end (migration to BIGINT)#896
Merged
Conversation
PostHog team ids are integers (Team.id), so PostHog naturally sends
`"default_team_id": 12345` — the string-typed field 400ed that obvious
payload at provision time ('json: cannot unmarshal number into Go
struct field provisionRequest.default_team_id of type string'), breaking
a beta customer's warehouse provisioning. Rather than stringify on the
caller side, make the type match the source of truth everywhere:
- Migration 000017: duckgres_orgs.default_team_id VARCHAR → BIGINT
(NULL stays NULL; all stored values are numeric strings, a non-numeric
one fails the migration loudly) and the compute-usage bucket key's
team_id TEXT → BIGINT (0 = 'no default team' sentinel, it sits in the
PK so it can't be NULL). USING clauses go through ::text so a goose
replay onto an already-converted schema still type-checks.
- Org.DefaultTeamID *int64, OrgConfig/OrgDefaultTeamID int64 (0=unset);
meter + bucket key + pull-API response carry team_id as an integer
(JSON number).
- Provisioning API: default_team_id is a JSON number; a quoted string is
now a decode-time 400 naming the field (contract test pins it).
- Admin API: *int64 — nil preserves, 0 (or explicit JSON null) clears,
n sets; audit detail renders (unset) for nil/0. Admin UI input keeps
the text field but validates digits-only and sends number/0.
- e2e: provision bodies send numbers, PUT set/clear/restore uses
424242/0/number, the billing pull round-trip compares team_id
numerically.
No PostHog-side change needed — its current integer payload becomes
correct once this deploys.
Test Impact PlanDeterministic summary of how this PR changes tests, CI runners, and coverage-risk signals. Summary
Signals
Coverage risk: needs review Warnings
|
ALTER TYPE cannot auto-cast the usage table's '' column default to BIGINT (42804) — drop the default before the type change (both directions). The v8-upgrade test now also reverses 000015/000017 on the compute-usage buffer (back to the v7 (org_id, bucket_start) shape + drain-state table) before replaying, mirroring what it already does for the org columns — replaying 000015's ''-keyed backfill onto an already-BIGINT team_id column cannot work and a real v8 database never has those columns anyway. Verified against a real local Postgres.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the beta-customer provisioning failure:
Failed to provision warehouse: json: cannot unmarshal number into Go struct field provisionRequest.default_team_id of type string.PostHog team ids are integers (
Team.id), so PostHog naturally sends"default_team_id": 12345— and the string-typed field 400ed the obvious payload. Instead of stringifying on the caller side, this makes the type match the source of truth end-to-end. No PostHog-side change needed — its current payload becomes correct once this deploys.What changed
duckgres_orgs.default_team_idVARCHAR → BIGINT (NULL stays NULL; all stored values are numeric strings — a non-numeric one fails the migration loudly, which is the correct outcome). The compute-usage bucket key'steam_idTEXT → BIGINT with0= "no default team" (it sits in the PK, so it can't be NULL).USINGclauses go through::textso a goose replay onto an already-converted schema (the v8-upgrade test does this) still type-checks.Org.DefaultTeamID *int64, snapshot/OrgDefaultTeamIDint64(0 = unset); the meter, bucket key, andGET /billing/usageresponse carryteam_idas a JSON number (billing joins get an int).default_team_idis a JSON number. A quoted string is now a decode-time 400 naming the field — contract testTestProvisionRejectsStringDefaultTeamIDpins it.*int64— absent preserves,0(or explicit JSONnull) clears to NULL,nsets. Audit detail renders(unset)for nil/0."team_id": 12345), CLAUDE.md billing section (+ fixed three stray\'escapes that slipped into it in Replace compute-billing push with the pull API #893).Tests
TestProvisionAcceptsNumericDefaultTeamID-equivalent coverage via all provision-body tests now sending numbers;TestProvisionRejectsStringDefaultTeamID(400 + nothing created); reprovision-keeps + mandatory tests updated toint64.migrations_postgres_test: v17, newrequireColumnTypeasserts BIGINT on both columns (incl. the replay/upgrade path).harness.sh: provision bodies send JSON numbers, admin PUT set/clear/restore uses424242/0/number with NULL round-trip, billing pull round-trip comparesteam_idnumerically (--argjson).🤖 Generated with Claude Code