Build Receipt ⏱️ 4 min read

One Missed Line of SQL Killed Agent Kombat. One-Shot Caught It.

TL;DR

💥 The problem: Clicking START SESSION on the Kombat arena page returned start failed — a CHECK constraint rejected every insert.
🔧 The fix: A 33-line migration that relaxed arena_agents_game_type_check to allow the new 'kombat' value.
The result: 11m 26s, $49.10 in API spend, composite 0.971 — and the protocol caught three related constraints I would have forgotten about.

ONE LINE BROKE START · 33 LINES FIXED IT · AUDIT CAUGHT 3 COUSINS

💥 What Broke

Agent Kombat is the 2.5D Mortal-Kombat-style fighter we shipped alongside the tug-of-war arena. Players pick SENTINEL or INFERNO, submit their agent, and watch the server resolve a deterministic damage matrix turn by turn.

That was the plan. The actual experience last night was: sign in, fill the form, click START SESSION, see start failed: insert: new row for relation "arena_agents" violates check constraint "arena_agents_game_type_check" in red.

👤 User clicks START SESSION

🧠 Edge function builds insert row game_type: 'kombat'

🚫 Postgres rejects: constraint only permits 'tug-of-war'

💥 Whole matchmaker aborts, UI shows start failed

🔍 Why It Happened

The arena started as a single-game system — tug-of-war only — so migration 013 locked the constraint to one value: check (game_type in ('tug-of-war')). When we added Kombat, migration 027 relaxed two neighbouring checks (weight_class on the agents table, game_type on the matches table) but missed the game_type check on the agents table itself.

One line. Every Kombat signup died on it.

Think of it like an electrician called to replace a blown fuse. The obvious fix is to swap the bad fuse. The careful fix is to pull the panel cover and check the other three on the same circuit — because whoever miswired this one probably skipped its cousins too. That's what the harden phase does.

🔧 The Fix

One new migration, dropped and re-added idempotently so it's safe to re-run. This mirrors exactly how migration 027 handled its own constraint swaps:

do $$
begin
  if exists (
    select 1 from information_schema.table_constraints
    where table_schema = 'public' and table_name = 'arena_agents'
      and constraint_name = 'arena_agents_game_type_check'
  ) then
    alter table public.arena_agents drop constraint arena_agents_game_type_check;
  end if;
  alter table public.arena_agents
    add constraint arena_agents_game_type_check
    check (game_type in ('tug-of-war','kombat'));
exception
  when others then null;
end $$;

After it landed, the constraint definition in Postgres was exactly what we wanted: CHECK ((game_type = ANY (ARRAY['tug-of-war'::text, 'kombat'::text]))).

📊 Build Stats

This is the run-report block verbatim from node ~/.claude/scripts/run-report.js end one-shot-scripts:

─── Run Report ────────────────────────────── Skill/label : one-shot-scripts Started : 2026-04-18T07:28:54.122Z Ended : 2026-04-18T07:40:20.424Z Time taken : 11m 26s Input tokens : 68,818 Output tokens : 114,995 Cache read : 13,174,822 Cache created : 1,049,774 Total tokens : 14,408,409 Estimated cost : $49.10 USD Assistant turns : 148 ─────────────────────────────────────────────

That's $49 to fix one line of SQL — obviously "overkill" if you stop there. The value sits in the next section.

📋 Dimension Scorecard

/one-shot-scripts blocks delivery until every dimension clears 0.85 and the composite hits 0.92. Here's how the fix scored:

DimensionScoreNotes
Correctness1.00Positive insert with 'kombat' succeeded, negative insert still rejected
Completeness1.00Migration on disk, applied to remote, registered in schema_migrations
Scope discipline1.00Did not touch the unrelated version-002 collision that blocked db push
Hardening sweep0.95Audited every game_type and weight_class constraint in the DB
Verification0.90DB-level test confirmed; user-side click through still pending
Documentation0.95Migration header explains bug, fix, idempotency in six lines
Polish1.00No stray debug, mirrors the 027 pattern exactly
Composite0.97Above threshold, no fix loop needed

🛡️ What the Harden Phase Actually Did

This is where the $49 earns its keep. After the obvious fix was in, the protocol walked every check constraint on the arena tables — it found four that matter, and flagged what state each one was in:

ConstraintStateAction
arena_agents.game_typeBrokenFixed by this migration
arena_agents.weight_classAlready fixed in 027Leave
arena_matches.game_typeAlready fixed in 027Leave
monthly_periods.weight_classNo 'kombat' supportNote — Kombat doesn't write here yet
arena_agents.game_type
BROKEN — rejects 'kombat'
Check expression at point of failure
check (game_type in ('tug-of-war'))
FAIL · row { game_type: 'kombat' } violates check_constraint — only 'tug-of-war' permitted. This is the bug.
← click any constraint to inspect

Three of those four are "not broken, but adjacent". Without the sweep, the first time a user hit monthly rewards for a Kombat match, the same class of bug would have surfaced and someone would have had to diagnose it from scratch. Now it's on the record with a scope decision: "fine for now, migration due when Kombat wires into monthly."

Caught

The unrelated version-002 collision (002_rls_policies.sql and 002_self_enroll_rpc.sql both claim version 002) blocks supabase db push. Instead of fixing that stale problem, the fix routed around it with supabase db query --linked --file — direct execution through the management API.

Caught

After applying via db query, the migration wasn't registered in schema_migrations — which would have caused a future push to try to re-apply it. supabase migration repair --status applied 028 --linked fixed that in one call.

⚠️ The Honest One: Verification 0.90

I ran a real insert with 'kombat' inside a rolled-back transaction, and it went through; I ran a second one with 'bogus-type', and it still hit the constraint. Both proofs are in the session log.

What I didn't do: click START SESSION in the actual browser as a signed-in user. The screenshot phase confirmed the page was in a clean "ready to fight" state after the fix, but the click-through test requires the user's own session and doesn't happen headlessly. That's the 0.10 gap.

🎯 When Max-Effort on a Tiny Bug Pays Off

A senior engineer fixing this manually writes the migration, pushes it, reloads the page, calls it done — maybe 10 minutes and $0. Fair benchmark.

What the protocol buys on top: the map of every related constraint, the reason each one is in the state it's in, a scope decision on the ones that aren't broken yet, and the trick for routing around the orphan version-002 collision that was about to bite someone else. All written down and grepable for the next time this shape of bug shows up.

Run the Same Protocol

/one-shot-scripts runs the full 8-phase protocol on whatever you point it at — from one-line bugs to 10,000-line features.

See One-Shot Scripts Get Access →