Deploy and Migration Accidents
Claude Code deploy accidents that hurt most aren't dramatic deletions but quiet ones: success messages that did nothing, tests that ran in production. Fixes included.
Agent production accidents bring to mind dropped databases. What cost us more in our test environment were quiet ones: “success” that changed nothing, a test that ran in production. They’re noticed late and are harder to undo. Reproductions used dummy files in a local temp directory (2026-10-16).
“Up to date” isn’t success
A migration command finished with an “up to date” success message, yet the change wasn’t there; the agent reported it applied. Many migration tools record applied migrations in a tracking table and run only what isn’t recorded, so if the record says applied, nothing runs. A dummy imitating just that logic in SQLite:
Remote database is up to date.
items テーブル: 0
Judge success by whether “Applying migration…” lines appeared, then check the real schema.
Public examples exist. Supabase CLI’s docs, for example, explain that db push compares local migration files against a tracking table and runs only those not yet applied (https://supabase.com/docs/guides/deployment/database-migrations, checked 2026-10-16), and a GitHub issue reports supabase db push printing Remote database is up to date. while the remote changes were missing (https://github.com/supabase/cli/issues/2136, checked 2026-10-16). The dummy output above borrows that message.
Rollback may only touch the record
The Supabase CLI docs above warn that migration repair updates the tracking table only and does not apply or revert SQL (same source). Fixing the record isn’t reverting the schema; write a reverse migration, apply it, verify.
If merge means deploy, migrate first
When merging to main deploys immediately, apply migrations to production before merging, written to work with old code too (add columns first, drop later).
Datetimes without time zones
PostgreSQL treats a time-zone-less input to a time-zone-aware type as being in the system TimeZone setting before converting to UTC (https://www.postgresql.org/docs/current/datatype-datetime.html, checked 2026-10-16). Different settings locally and in production shift times silently. Pass time-zone-aware datetimes. We did not reproduce this locally; it is based on the PostgreSQL docs.
A test that ran in production
We put a hand-written test job in a production queue; production picked it up and actually posted a message, which we couldn’t delete. We replaced the prose rule with a mechanism: production sends refuse non-interactive callers.
== non-interactive (cron-like) to prod
refuse: 対話端末以外から本番には送らない
exit=4
== non-interactive to test
send to test: (dummy)
exit=0
Test with a separate state directory or a no-send mode; quarantine jobs that didn’t come from the real scheduler.
Delete by name, not glob
An agent’s glob rm swept in a committed document. With dummy files, draft-* expanded to the three drafts plus draft-notes-source.md. List filenames explicitly and use rm -v.
Splitting production permissions
All of these happened because the agent could write to production. Agents read, humans write after checking: Permission Design for AI Agents. For running near-production work on an always-on machine: Run an AI Agent 24/7 on a VPS.
Summary
- “Up to date” isn’t success; look for applied lines and check the real schema.
- Fixing the record isn’t reverting; apply reverse SQL.
- If merge deploys, migrate first, compatible with old code.
- Pass time-zone-aware datetimes.
- Refuse production sends from non-interactive callers.
- Delete by explicit names, not globs.