Skip to content

default_team_id is an integer end-to-end (migration to BIGINT)#896

Merged
benben merged 2 commits into
mainfrom
ben/default-team-id-int
Jul 6, 2026
Merged

default_team_id is an integer end-to-end (migration to BIGINT)#896
benben merged 2 commits into
mainfrom
ben/default-team-id-int

Conversation

@benben

@benben benben commented Jul 6, 2026

Copy link
Copy Markdown
Member

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

  • 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, which is the correct outcome). The compute-usage bucket key's team_id TEXT → BIGINT with 0 = "no default team" (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 (the v8-upgrade test does this) still type-checks.
  • Model/plumbing: Org.DefaultTeamID *int64, snapshot/OrgDefaultTeamID int64 (0 = unset); the meter, bucket key, and GET /billing/usage response carry team_id as a JSON number (billing joins get an int).
  • Provisioning API: default_team_id is a JSON number. A quoted string is now a decode-time 400 naming the field — contract test TestProvisionRejectsStringDefaultTeamID pins it.
  • Admin API: *int64 — absent preserves, 0 (or explicit JSON null) clears to NULL, n sets. Audit detail renders (unset) for nil/0.
  • Admin UI: text input kept, digits-only validation on save (junk can no longer silently clear), sends number / 0-to-clear; org list + detail types updated.
  • Docs: pull-API spec examples/key description ("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 to int64.
  • Meter/billing-API unit tests: integer team ids in keys and rows.
  • migrations_postgres_test: v17, new requireColumnType asserts BIGINT on both columns (incl. the replay/upgrade path).
  • e2e harness.sh: provision bodies send JSON numbers, admin PUT set/clear/restore uses 424242/0/number with NULL round-trip, billing pull round-trip compares team_id numerically (--argjson).
  • UI: typecheck, 48 vitest tests, build — green.

🤖 Generated with Claude Code

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.
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

Test Impact Plan

Deterministic summary of how this PR changes tests, CI runners, and coverage-risk signals.

Summary

Area Added Changed Deleted
Test files 0 6 0
E2E/journey files 0 1 0
Workflow files 0 0 0

Signals

  • Test cases: +1 / -0
  • Assertions: +10 / -4
  • Skips or known failures added: 0
  • Workflow continue-on-error added: 0
  • Workflow path filters added: 0
  • Test commands removed from justfile: 0
  • E2E/journey retry lines added: 0

Coverage risk: needs review

Warnings

  • E2E or journey files changed (needs review)
    • tests/e2e-mw-dev/harness.sh

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.
@benben benben merged commit d7e618c into main Jul 6, 2026
28 checks passed
@benben benben deleted the ben/default-team-id-int branch July 6, 2026 15:30
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.

1 participant