What is Claude Code? I Gave It a Migration I'd Dodged for 4 Months

I had a Postgres migration sitting in my backlog since June. JSONB blob holding Serbian VAT data, 80,000 live rows, foreign keys I'd half-forgotten, and a two-minute downtime budget. I'd estimated six hours and kept pushing it to "next Saturday." Claude Code finished it in 38 minutes — and caught one orphaned-row bug I would have shipped broken.
This isn't a Claude Code intro post. There are fifty of those. This is what actually happens when you point it at production code you're scared to touch.
Claude.ai vs Claude Code — Stop Confusing Them
Claude.ai is the browser chat. You paste code in, it talks back. Useful, but it's a copy-paste loop with you as the integration layer.
Claude Code is a different product. It runs in your terminal, reads files on disk, edits them, executes commands, reads the command output, and decides what to do next. That last part is the whole point. It's a loop:
plan → act → observe → iterate
Not autocomplete. Not a chatbot. An agent with file system access and a shell. When it runs pytest and 3 tests fail, it reads the failure output and tries to fix them. When alembic upgrade head errors, it reads the traceback and adjusts the migration.
You install it once:
npm install -g @anthropic-ai/claude-code
cd ~/projects/your-repo
claude
That's the entire setup. From there it works inside whatever directory you launched it from.
The Real Superpower Is Reading, Not Writing
Anthropic markets Claude Code as a coding assistant. Fine. But for anyone running a SaaS they built a year ago, the killer feature isn't generating new code. It's reading code you forgot you wrote and explaining it back before changing anything.
The bottleneck for solo founders and small teams isn't typing speed. It's the fear of touching legacy code. You wrote the invoice service in February, it's been running fine for nine months, and now you need to add VAT columns. You open the file and immediately lose 40 minutes re-learning what _compute_line_totals() actually does and which three service classes call it.
Claude Code reads the whole graph in about four minutes. It opens models, migration history, service classes, and — this is the part that surprised me — the PDF generator, because it grepped for references to the column I was about to change. Then it tells you, in plain language, what touches what. Before any edit.
What changes about your workflow
- You stop avoiding the scary files in your repo
- You stop re-reading code to remember what it does
- You start describing intent, not writing diffs by hand
- Code review shifts from "did this compile?" to "is this plan correct?"
The Migration That Sat in My Backlog for 4 Months
The client: a small invoicing business in Serbia. 9,400 lines of Python, FastAPI, Postgres, two years of organic schema growth. The problem: the original invoice_items table stored VAT rate, VAT amount, and net amount inside a single jsonb column called vat_data. Serbian tax rules changed. We needed three proper columns with proper indexes, plus a backfill for 80,000 existing rows.
Constraints:
- Live customers, no downtime window longer than 2 minutes
- Production runs on a small managed Postgres instance — lock contention is real
- Old code paths still write to the JSONB column and I can't deploy everything at once
My estimate: 6 hours minimum. Probably a full Saturday.
What I actually did was open the terminal in the project root, run claude, and give it one prompt:
Read the invoice_items table definition, read every model and query that
touches it, and propose a migration plan that:
1. splits the jsonb vat_data into three new columns (vat_rate, vat_amount, net_amount)
2. backfills the 80k existing rows safely
3. keeps the old jsonb column in place until I confirm cutover
Do not write any code yet. Just give me the plan.
The last sentence matters. Always make it plan before it acts on anything risky. Always.
The Plan-First Pattern (And Why You Must Enforce It)
It spent 4 minutes reading. Opened the models file, the alembic/versions/ directory, three service classes, the PDF generator, and even a Celery task that nightly-aggregated VAT for reporting. Came back with a seven-step plan:
- New Alembic migration adding
vat_rate,vat_amount,net_amountas nullable columns - Backfill script processing rows in batches of 1000, reading old JSONB and populating the three columns
- Verification query: sum-compare old JSONB values vs. new columns across all 80k rows
- Application-layer dual-write — new invoices populate both old and new during transition
- Read-path migration: switch queries to read from new columns, keep dual-write
- Soak period (I picked 7 days)
- Cutover migration: drop the old JSONB column, gated behind manual approval
I read it. Two corrections:
- Batch size 500, not 1000. Small instance, don't want lock contention.
- Log row count before and after each batch to a file so I can audit later.
It updated the plan. I said go.
Why "plan first" beats "just do it"
- You catch wrong assumptions in 30 seconds of reading vs. 30 minutes of debugging
- You can tune parameters (batch size, indexes, ordering) before any DDL runs
- You get a written record of intent — useful when something breaks at 2am next month
- It forces the agent to read the whole context, not pattern-match on the file you opened
Here's roughly what the generated Alembic migration looked like:
def upgrade():
op.add_column('invoice_items',
sa.Column('vat_rate', sa.Numeric(5, 2), nullable=True))
op.add_column('invoice_items',
sa.Column('vat_amount', sa.Numeric(12, 2), nullable=True))
op.add_column('invoice_items',
sa.Column('net_amount', sa.Numeric(12, 2), nullable=True))
op.create_index('ix_invoice_items_vat_rate',
'invoice_items', ['vat_rate'])
def downgrade():
op.drop_index('ix_invoice_items_vat_rate')
op.drop_column('invoice_items', 'net_amount')
op.drop_column('invoice_items', 'vat_amount')
op.drop_column('invoice_items', 'vat_rate')
And the backfill loop:
BATCH = 500
log = open('backfill_audit.log', 'a')
while True:
rows = session.execute(text("""
SELECT id, vat_data FROM invoice_items
WHERE vat_rate IS NULL AND vat_data IS NOT NULL
ORDER BY id LIMIT :batch
"""), {"batch": BATCH}).fetchall()
if not rows:
break
log.write(f"batch_start ids={rows[0].id}..{rows[-1].id} count={len(rows)}\n")
for r in rows:
d = r.vat_data
session.execute(text("""
UPDATE invoice_items
SET vat_rate = :rate, vat_amount = :amt, net_amount = :net
WHERE id = :id
"""), {"rate": d["rate"], "amt": d["amount"],
"net": d["net"], "id": r.id})
session.commit()
log.write(f"batch_done count={len(rows)}\n")
It ran the migration against my local dev database (refreshed copy of prod from the previous week). Ran the backfill. Ran the verification — sums matched on all 80,000 rows.
The Orphan Row It Caught That I Didn't Ask About
This is the part that earned its keep.
Before suggesting we drop the old vat_data column, it ran one extra check I had not asked for. It queried invoice_items rows whose invoice_id didn't resolve to a row in invoices. Orphans.
It found 11.
Eleven rows created two years ago by a buggy CSV import script that never cleaned up after itself. If I had dropped the JSONB column and run any kind of foreign key tightening later, those rows would have either failed silently or cascade-deleted into something ugly when someone eventually ran a DELETE FROM invoices WHERE ....
It flagged them, printed the IDs, asked what I wanted to do. I told it to archive them to invoice_items_archive_2023 and delete from the live table. Done.
Total wall-clock time: 38 minutes. That includes me reading the plan, making two corrections, and verifying every output.
I want to be clear about why this matters. I have been doing this job long enough to know that the orphan check is exactly the kind of thing a tired engineer skips at 11pm on a Saturday. The agent didn't skip it. Not because it's smarter than me — because it has no ego and no rush.
When NOT to Use Claude Code
This tool is sharp. It also cuts the wrong way if you point it at the wrong problem. From running it daily on client production code, here's where I won't use it:
- Greenfield architecture decisions. It will give you an answer. The answer will be average. Architecture is where you earn your salary as the builder.
- Payment processing code without a human review gate. Stripe webhooks, refund logic, anything that moves money — read every line yourself, every time.
- Any repo without git. If it goes off the rails, you want one command to undo everything.
git reset --hardis your safety net. No git, no agent. - Tasks with ambiguous success criteria. "Make the UI better" — no. "Add an aria-label to every button missing one and run axe-core to verify" — yes.
The sweet spot
- Legacy migrations (the one above)
- Dependency upgrades (Python 3.10 → 3.12, FastAPI minor versions)
- Refactors that touch 30 files but are mechanically obvious
- Writing tests for code that has none
- Documenting code nobody on the team remembers
Boring, high-value, scary work that you keep pushing to next week. That's the lane.
Why bizflowai.io helps with this
A lot of what I do for clients at bizflowai.io is exactly this pattern — taking the migrations, refactors, and integration plumbing that's been sitting in their backlog for months and shipping it under a structured plan-first loop, with human review gates on anything that touches money or customer data. The agent does the reading and the typing. I do the architecture, the review, and the call on whether a plan is actually safe to run against production. That's the division of labor that makes it work.
Frequently asked questions
What is Claude Code and how is it different from Claude.ai?
Claude.ai is a browser-based chat window. Claude Code is a separate product: an agent that runs in your terminal, reads files on your machine, edits them, executes commands, observes the output, and decides what to do next. It operates on a plan-act-observe-iterate loop. It is not autocomplete or a chatbot pretending to code, but an agent that interacts directly with your codebase.
What is the most valuable use case for Claude Code on existing projects?
The strongest use case is not writing new code, but reading and explaining legacy code you wrote months ago before changing it. For small SaaS operators or internal tool maintainers, this removes the main bottleneck, which is the fear of touching code you no longer remember. Claude Code can map models, queries, and dependencies across files before proposing any modifications.
How do I safely use Claude Code for a risky database migration?
Always make Claude Code plan before it acts on risky changes. Open the terminal in your project root, run claude, and explicitly instruct it to read the relevant tables, models, and queries, then propose a migration plan without writing code yet. Review the plan, make corrections like batch sizes or logging requirements, and only then approve execution. Gate destructive steps like dropping columns behind manual approval.
Why does plan-first prompting matter when using Claude Code?
Plan-first prompting prevents Claude Code from making irreversible changes before you understand the scope. In a real Postgres migration splitting a jsonb VAT column across 80,000 rows, planning first surfaced a seven-step approach including backfill batching, verification queries, dual-writes, and a gated cutover. It also let the developer correct batch size and add logging before any code was written or executed.
How long does a Claude Code migration take compared to manual work?
In a documented case involving a 9,400-line Python invoicing app with 80,000 rows to backfill, the developer estimated six hours minimum for manual work. Using Claude Code with a plan-first approach, the total wall-clock time was 38 minutes, including time to read the plan, make two corrections, and review a flagged data issue with 11 orphaned rows the agent caught unprompted.
Want more like this?
I publish practical AI automation, GenAI engineering, and faceless content workflows on YouTube every week.
Subscribe to bizflowai.io on YouTube — never miss a new tutorial.
Planning an AI automation project or need a second opinion on your architecture?
Connect with me on LinkedIn — Lazar Milicevic, GenAI Engineer & bizflowai.io Founder.
Visit bizflowai.io for our services, case studies, and AI consulting.
Frequently asked questions
What is Claude Code and how is it different from Claude.ai?
Claude.ai is a browser-based chat window. Claude Code is a separate product: an agent that runs in your terminal, reads files on your machine, edits them, executes commands, observes the output, and decides what to do next. It operates on a plan-act-observe-iterate loop. It is not autocomplete or a chatbot pretending to code, but an agent that interacts directly with your codebase.
What is the most valuable use case for Claude Code on existing projects?
The strongest use case is not writing new code, but reading and explaining legacy code you wrote months ago before changing it. For small SaaS operators or internal tool maintainers, this removes the main bottleneck, which is the fear of touching code you no longer remember. Claude Code can map models, queries, and dependencies across files before proposing any modifications.
How do I safely use Claude Code for a risky database migration?
Always make Claude Code plan before it acts on risky changes. Open the terminal in your project root, run claude, and explicitly instruct it to read the relevant tables, models, and queries, then propose a migration plan without writing code yet. Review the plan, make corrections like batch sizes or logging requirements, and only then approve execution. Gate destructive steps like dropping columns behind manual approval.
Why does plan-first prompting matter when using Claude Code?
Plan-first prompting prevents Claude Code from making irreversible changes before you understand the scope. In a real Postgres migration splitting a jsonb VAT column across 80,000 rows, planning first surfaced a seven-step approach including backfill batching, verification queries, dual-writes, and a gated cutover. It also let the developer correct batch size and add logging before any code was written or executed.
How long does a Claude Code migration take compared to manual work?
In a documented case involving a 9,400-line Python invoicing app with 80,000 rows to backfill, the developer estimated six hours minimum for manual work. Using Claude Code with a plan-first approach, the total wall-clock time was 38 minutes, including time to read the plan, make two corrections, and review a flagged data issue with 11 orphaned rows the agent caught unprompted.