Follow a failed deploy from error to artifact
It’s the 2am page from Module 0. Someone tightened an index to UNIQUE, the deploy ran, and it stopped cold — two customers in the table share an email, and you just told the database that column has to be one of a kind. The deploy came back exit 2. Now what?
Module 0 promised the trail was always there. This module walks it — the whole way, hands on.
Hey folks. I’m Forge Barrett, master of the Content Forge here at SchemaSmith.
This is Module 1 of Course 8, and it’s your first induced failure. We’re going to trigger a real, deterministic break and read every piece of evidence it leaves: the exit code, the log, the artifact, the phase name. By the end, “I don’t know why it failed” turns into “the unique index couldn’t be created because the data has duplicates” — and you’ll know exactly where you read that.
Triage on the exit code
Section titled “Triage on the exit code”First thing, every time: the exit code.
0— success. Nothing to diagnose.2— a quench failed. This is a diagnostic case. Something the engine ran got rejected.
Module 0 covered the full table; here we apply it. Exit 2 means stop and read — and it tells you this is a phase failure, not a crash. The tool did its job and the database said no.
The black box, summarized — open Failures.log first
Section titled “The black box, summarized — open Failures.log first”Before you dig into the interleaved run narrative, open SchemaQuench - Failures.log. It is the black box, summarized: every failure gets one block — the error message, a Debug SQL: pointer to the artifact, and a Context (last 25 lines) phase trail that names exactly where execution stopped. For a single-database incident you can answer the whole “what broke, where, and what do I open next?” from one file.
Here’s the real output from the lab’s induced failure on SQL Server:
1 failure(s): 1 Template:Main
─── FAILED [Template:Main] [localhost,11433].[diag_blackbox] ───Error: The CREATE UNIQUE INDEX statement terminated because a duplicate key was found for the object name 'dbo.Customer' and the index name 'IX_Customer_Email'. The duplicate key value is (ana.f@shop.test).Debug SQL: ./artifacts\SchemaQuench - Quench Indexes localhost,11433.diag_blackbox.sqlContext (last 25 lines): Detect Statistics Changes Drop Modified Statistics Drop Statistics No Longer Part of The Product Definition Collect Existing Check Constraints Detect Column Level Check Constraint Changes Detect Table Level Check Constraint Changes Drop Modified Check Constraints Drop Check Constraints No Longer Part of The Product Definition Alter Modified Columns Identify Existing Clustered Index Conflicts Drop Conflicting Clustered Index Drop Modified or Removed FullText Indexes Enable/Disable CDC Quenching object scripts Quenching between table and keys scripts Quenching indexes and constraints Collect index level extended properties Add New Computed Columns Add Missing Indexes Creating index [dbo].[Customer].[IX_Customer_Email] The CREATE UNIQUE INDEX statement terminated because a duplicate key was found for the object name 'dbo.Customer' and the index name 'IX_Customer_Email'. The duplicate key value is (ana.f@shop.test).
Resolved SQL written to: ./artifacts\SchemaQuench - Quench Indexes localhost,11433.diag_blackbox.sql FAILED to quench:The CREATE UNIQUE INDEX statement terminated because a duplicate key was found for the object name 'dbo.Customer' and the index name 'IX_Customer_Email'. The duplicate key value is (ana.f@shop.test). Resolved SQL written to: ./artifacts\SchemaQuench - Quench Indexes localhost,11433.diag_blackbox.sqlRead that block once and you already know the whole story: Failures.log reports the error, tells you the artifact to open, and the phase trail shows you where execution landed — Quenching indexes and constraints → Add Missing Indexes → Creating index [dbo].[Customer].[IX_Customer_Email] — before the database said no. The Debug SQL: line is your direct pointer to the artifact. No searching, no scrolling.
Locate — the log names the file
Section titled “Locate — the log names the file”The deeper read: open SchemaQuench - Progress.log. Scroll to the bottom and find the FAILED to quench: block. On all three engines, the engine’s own error sits right inside it:
SQL Server:
The CREATE UNIQUE INDEX statement terminated because a duplicate key was foundfor the object name 'dbo.Customer' and the index name 'IX_Customer_Email'.The duplicate key value is (ana.f@shop.test).PostgreSQL:
23505: could not create unique index "ix_customer_email"MySQL:
Duplicate entry 'ana.f@shop.test' for key 'Customer.IX_Customer_Email'Three engines, three phrasings, one verdict: a duplicate value where a unique index needs distinct ones. SQL Server names the colliding value; PostgreSQL prints the SQLSTATE 23505; MySQL prints the classic 1062 message. Read whichever one you’re on — they all say the same thing.
One thing to know about where to read: on SQL Server the error also lands in SchemaQuench - Errors.log. On PostgreSQL and MySQL that file stays empty — the detail lives in Progress.log. So Progress.log’s FAILED to quench: block is the one place that works on every engine. Read there and you’re never guessing which file to open.
Just above the error, the log points you at the artifact:
Resolved SQL written to: ./artifacts/SchemaQuench - Quench Indexes ….diag_blackbox.sqlThat’s the “locate” step made concrete — the log names the exact file to open next.
Read — the artifact reproduces it by hand
Section titled “Read — the artifact reproduces it by hand”Open that Quench Indexes artifact. For a mechanical phase like this one, it’s a single copy-runnable proc call:
EXEC [diag_blackbox].SchemaSmith.MissingIndexesAndConstraintsQuench @ProductName = 'Shop', @WhatIf = 0Paste it into SSMS, psql, or the mysql client and it throws the same duplicate-key error, by hand. That’s the whole payoff Module 0 promised: the artifact isn’t a report about the failure — it’s the exact call the engine ran, ready to run again. When you want to watch the break happen in isolation, outside the full quench, this is what you run.
One thing you will not find here: a >>> FAILING BATCH marker. That belongs to a failed script — one of your own Before/After files — and we meet those in Module 4. A mechanical phase like the index build doesn’t write a flagged batch; it puts the error in Progress.log and hands you the one-line proc call. Different failure, different evidence shape.
Name the phase
Section titled “Name the phase”The artifact filename and the log both name it: Quench Indexes … / Quenching indexes and constraints. That’s the move that turns a wall of SQL into a sentence you can act on — the index phase couldn’t build a unique index because the data has duplicates.
And check checkpoints/. The failed run left a checkpoint file sitting there. Module 0’s healthy run deleted its checkpoint on success; this one kept it, because the run didn’t finish. Preserved-on-failure, deleted-on-success — file that away; it matters in Module 5.
Recover — fix the data, redeploy
Section titled “Recover — fix the data, redeploy”The index definition is right. The data isn’t unique yet. So clean the data — give the duplicate its own address:
UPDATE dbo.Customer SET Email = 'ana.f7@shop.test' WHERE CustomerId = 7;Then redeploy the same change that failed. Green — the unique index takes, on all four engines. And the leftover checkpoint? Deleted. A green run leaves none, exactly as Module 0 showed. You didn’t need any special recovery flag; a plain redeploy re-ran every phase, hit the now-clean data, and converged.
That’s the loop, start to finish: locate the phase (fast path: Failures.log; full path: Progress.log’s FAILED to quench: block), read the engine’s error and reproduce it from the artifact, recover by fixing what the engine objected to.
Check yourself: Your quench exits 2 on PostgreSQL, but SchemaQuench - Errors.log is completely empty. Did the error not get logged — and where do you actually read it?
The error was logged — just not where you looked. Errors.log is only populated on SQL Server. On PostgreSQL and MySQL the engine’s error lands in SchemaQuench - Progress.log, inside the FAILED to quench: block, along with the Resolved SQL written to: line pointing at the phase artifact. That’s why Progress.log is the cross-engine place to read: it carries the failure detail on all four engines, while Errors.log is a SQL-Server-only mirror. An empty Errors.log on PostgreSQL means nothing’s wrong with your logging — you’re just reading the wrong file.
What’s next
Section titled “What’s next”You’ve read one failure end to end. The map has more stops:
- Module 2 · Structure-change failures — required columns added over existing data, narrowing a column that still holds long values. The structure phases, and why one engine’s silence is more dangerous than another’s error.
- Module 3 · Index, constraint & FK failures — the full anatomy of this incident and its cousins: why a changed index fails one phase later than you’d think, and the foreign key that catches an orphan last.
- Module 5 · The recovery toolkit — for when the fix isn’t a one-line
UPDATE:--ResumeQuench, marking a script done, and the checkpoint you just watched get left behind.
A cracked piece isn’t a mystery to a smith who reads metal. The break has a grain, a start, a direction — look closely and it tells you what the fire was doing when it gave. A failed deploy is the same. Exit code, log block, artifact, phase name: read them in order and the black box was never black. It was just a trail you hadn’t walked yet.
Got a deploy that stopped on an index or a constraint and left you squinting at the log? Email me at forgebarrett@schemasmith.com — send me the Failures.log block (or the FAILED to quench: section from Progress.log) and I’ll help you name the phase.
Next up: Course 8 · Module 2 — Structure-change failures, where the engines stop agreeing on what “failure” even means.
Until then, may every break you meet leave a trail this plain to read.
— Forge