flowingkhaosSign inBrowse the marketplace
New here? flowingkhaos is a build-in-public workshop. This is its weekly column.What this site is

Claude Code Workflow: Why Your Green Gate Might Be Lying to You

Luke Sidneyupdated 18 min read

A green gate is a claim, not a receipt: my Claude Code workflow ran a migration in production twice and passed a run missing 3 of 10 tasks. Here's the fix.

The Answer: The Loop Works, the Gate Doesn’t

Look, I need us to have a serious talk about the exit code. Everyone pitching you a Claude Code workflow right now is selling the same three-beat loop: plan the work, let the agent implement it, run a validation gate, land if it’s green. And the loop itself? It works. I’ve run it. The gate it trusts is where it fails.

A brass scale tips toward a stack of paper receipts, with a lone iron key on the opposite pan.

Here’s my receipt. One of my runs finished green with 3 of its 10 tasks never written. Five of ten HTTP routes: missing. A manifest and its guard test: missing. An acceptance node: missing. The gate checked lint, types and tests, and it had no check that a file even exists. It also skipped the build and a whole test level because environment variables were unset. Green light, a third of the work undone. I found out by reading the task list, which the loop never looks at, because the loop’s exit signal is the gate’s exit code.

So here’s the rule, up front, because you will be tempted to skip it: before you land anything, read the gate’s SKIPPED lines, then list the files each task says it creates and confirm they exist on disk. Two minutes. Do it every time.

Let’s get one thing straight: a green gate is a claim. It is not a receipt. And Anthropic’s own advice, which I genuinely agree with, is built on the assumption that the claim is true. Their best-practices guide tells you to provide verification criteria and to run the tests after implementing, so the agent can fix what breaks. Correct advice! It’s also advice that silently assumes the verifier is correct. I’m here to tell you mine wasn’t. Three runs of mine died to a gate that couldn’t pass at all. One reached production. The gate is the most likely component in your workflow to lie to you, and it’s the only one the loop treats as gospel.

Here’s the shape of what’s coming. One failure class where runs touched a live production database, and the per-second cost of the fix (measured, five rounds: a per-run Postgres container costs 12.8–14.7 seconds to start and 0.9 to remove). One failure class where green meant undone. One failure class where the gate broke itself fifteen distinct ways, and I’ll name five. Then the rules that came out of all of it, because I like my lessons expensive but only paying for them once.

How My Claude Code Workflow Is Supposed to Work

You’ve read the pitch, you’re sold on the loop, and now you want the mechanics. Fine. Here’s how mine actually runs, and I’m giving it to you straight because the plumbing matters more than the vibes.

The prp-piv loop and where a red gate sends it back
  1. 01ResearchReads the codebase
  2. 02PlanWrites tasks and the gate, validate.sh
  3. 03ImplementWrites code; may not edit the gate
  4. 04ValidateRuns the gate script
  5. ↑ 03Back to ImplementA red gate sends the run back to implement because the code is wrong; the loop stops after 8 iterations.

My loop is called prp-piv, and it runs in Archon, an open-source workflow runner for Claude Code. The shape comes from Cole Medin’s notes on the PIV loop, and it’s a good shape. He lays out the same plan-implement-validate philosophy alongside siblings like GitHub’s Spec Kit, and if you’ve never seen these frameworks compared side by side, his page is honestly the best starting point. Everything in my setup runs in a worktree, which is just a second checkout of the same repo, so the agent’s chaos stays off your main working copy. Four steps. Research reads the codebase. Plan writes the task list and the gate script, validate.sh, which is the thing that will later decide if the run lives or dies. Implement writes code. Validate runs the gate. Red gate? Back to implement. Up to 8 iterations, and then the run gives up, which is worth knowing before you promise someone a feature by Friday.

Alex Kurkin’s version is the same skeleton, and I want to be specific about what he adds, because his write-up isn’t just a manifesto. His Research → Plan → Implement → Validate framework describes custom Claude Code commands that coordinate parallel sub-agents and persist findings across sessions, so each phase inherits the last phase’s context instead of starting cold. That’s a genuinely good idea, and I’ve stolen the spirit of it if not the implementation. But he also claims fix-it commits dropped to under 10% on his setup, and I need to flag that carefully: that’s his claim, from his own post, measured however he measured it, and I can’t verify it. I like the framework. I don’t co-sign the stat. My own numbers live in my own repos, and they’re messier than that.

The one rule you cannot skip

Now, one design decision that sounds boring and is actually load-bearing: the implement step may not edit the gate. That’s correct. If the agent could rewrite its own exit exam, you’d have a student grading their own test, and buddy, they are not grading themselves harshly. They do not feel shame. Build accordingly.

But here’s the consequence nobody mentions: if the gate itself is buggy, nothing inside the loop can fix it. Eight iterations of implement hammering against a broken gate, and the only fix is you, outside the loop, reading the script. Which is exactly why you should read the gate before the long run starts, not after it burns all 8. I treat validate.sh like I’d treat a contract someone else is signing on my behalf. Clause by clause.

And why does any of this deserve the paranoia? Because of Anthropic’s own pitch, which leans hard on autonomy: the product is sold on multi-hour runs that keep working without you. That’s the product working as advertised, and it’s exactly the behavior that makes an untrusted gate dangerous. An unattended agent that keeps going, against a gate it trusts, in a repo with a live database attached? That’s not a workflow. That’s a dare.

The loop is the engine. The gate is the steering, and next section I’ll show you what happened when nobody was holding it.

Failure One: Runs Reached Production

I need to tell you about the worst kind of surprise: the one you find out about at land time via archaeology. It’s a Tuesday, your run is humming along in its nice little worktree, and somewhere in the middle of a build step, the agent runs payload migrate. Against your live production database. Hours before any human reviews anything. That’s not a hypothetical I invented for drama. That’s my 2026-08-12, and I found out when my pre-push check noticed the table already existed. It had landed as batch 4 in the production migration journal. My workflow had a paper trail. Wonderful. I love learning my agent shipped by doing forensics on my own database.

And because one production incident is a fluke but two is a pattern, we go again. 2026-08-26: a branch that was never merged created a table, agents.source_watermarks, in production through migration 0006. Then main’s own 0006 applied on top. Two different migrations wearing the same number, both real, both live. That one took a later migration to clean up.

Why the agent went looking for your .env.local

Here’s the part that made me want to lie down. The cause, which I measured on 2026-10-06, was not the runner being malicious or the agent being creative. Archon strips the repo’s env files from its own process, and has since v0.3.6. So when an agent or a gate needed a database mid-run, it went looking, found the main checkout’s .env.local, and used it. Your production credentials, quietly available to an unattended loop. There’s an old gate of mine that asserted test ! -e apps/cms/.env.local, because an agent had copied that file in at some point, and past-me was patching the symptom instead of the pipe. Past-me was cooking, but barely.

This is exactly where Anthropic’s pitch matters, and to be clear, this is their pitch, not my criticism of it. The Claude Code product page advertises multi-hour refactors and migrations where Claude runs your tests and keeps going when something breaks. I’ve read that copy a few times now, and each time the phrase “keeps going” jumps out at me, because that’s the whole selling point and also precisely the behavior that let my migration land. An agent that keeps going is great right up until “going” means “toward production.” Am I being too harsh on a feature working as designed? Probably. But the designed behavior ran my migration, so I get to be a little harsh.

The context that makes this genuinely scary is unattended runs. Claude Code’s GitHub Actions setup is built on claude-code-action. I’m not dunking on the feature. But it’s a permissions list, not an isolation boundary. It decides what the agent may call, not what the agent may reach, and it has no idea that a database URL sitting in an env file is a loaded weapon.

The fix, which I’ll price out in seconds next section, is dumber and better than anything clever I tried: every run gets its own empty Postgres container, and nothing in a run can see production again. Let’s be clear about the old setup: the problem was never that the agent was careless. The problem was that I left production credentials somewhere a very diligent agent could reach them. That’s on me.

The Fix and What Each Guard Costs in Seconds

So, the fix. And I want to be upfront: it’s boring. No clever sandboxing, no custom permission schema. Every run gets its own empty Postgres 17 container, built from the repo’s actual migration chain. The gate exports that database’s URL before it runs anything. And here’s the part I’m genuinely smug about: Bun and Next’s env loader never override a variable that’s already set, which I proved with loadEnvConfig, so even if a stray .env.local is lurking in reach, the run’s URL wins. The pipe is patched, not the symptom.

Now the money question, because I promised you receipts, not vibes. I measured this on 2026-10-07 across 5 rounds, and the bill is embarrassingly small:

Step Cost Wall-time impact
Start the run’s database 12.8–14.7 s Beside the research steps, so no added wall time
Full migration chain from empty 72 s, once per run (55 migrations, 55 rows) Per run
Load the run’s env before each gate About 0 s Per gate
Remove the container 0.9 s Per run

Twelve seconds, hidden inside work the run was doing anyway. Seventy-two seconds, once, for the whole chain. Nine-tenths of a second to clean up. That’s the entire cost of never again discovering my agent’s migrations via production archaeology. If you’re priced out of that, I don’t know what to tell you.

The measurement also caught me a bug in my own fix, because of course it did. The readiness check passed on the image’s first-boot server and failed during its restart in 1 of 3 starts. One-in-three flaky infrastructure is the kind of thing that’ll make you blame the loop forever, so now it’s checked over TCP: 5 of 5 since. (Ours, both numbers.)

And here’s a bonus I didn’t plan: an empty database is a stricter migration test than a copy of production. The 08-26 mess, two different migrations both numbered 0006, fails immediately on an empty chain, because you can’t apply main’s 0006 on top of a branch’s 0006 from scratch. The drift that slipped past a production copy gets caught on a clean build. Strictness for free.

Second net: a hook that holds any migration aimed at a production host, and in an unattended run, refuses it outright. Belt and suspenders, because suspenders alone failed me twice.

Shared databases cost me a gate at 00:53

Quick horror story to close the loop on why per-run isolation is non-negotiable. On 2026-08-25, two runs shared zero files but one database. One added a foreign key while the other was mid-flight. Result: the second run passed its gate at 00:43 and failed the same gate on an unchanged branch at 00:53. Ten minutes of reality shifting. Per-run databases make that whole failure class moot, which is why migrations now run alone.

Full honesty, since I promised it: a real prp-piv run using its own database hasn’t been observed yet. What I expect from the log is a named container and a final step that says removed. Expected, not seen. The moment it is, it goes in the record.

Failure Two: The Gate Lies

Let’s talk about the failure that almost got past me entirely. One run finished green with 3 of its 10 tasks never written. Five of ten HTTP routes: never written. A manifest and its guard test: never written. An acceptance node: never written. And the gate said everything was fine.

Here’s the autopsy. The gate checked lint, types and tests, which sounds like a responsible adult until you notice what it didn’t check: nothing verified that a file exists on disk. Worse, it skipped the build and an entire test level because environment variables were unset, so the checks it skipped weren’t even flagged as suspicious, they were just quietly absent. The loop’s exit signal is the gate’s exit code, not the task list. I’ll own it: I didn’t see that coming, and I built the thing.

So the rule, and it costs two minutes: before landing anything, read the gate’s SKIPPED lines, then list the files each task says it creates and confirm they exist. A green checkmark that checks the wrong things is not a receipt.

Failure Three: The Gate Breaks Itself Fifteen Ways

Now failure three, which is the funniest one if you have a sick sense of humor like me. My run notes from 2026-08-05 through 08-26 count 15 distinct ways a generated gate broke itself. A gate that can’t pass isn’t a safety net, it’s a trapdoor, and the implement step can’t fix it because the implement step may not edit the gate. Eight iterations of an agent hammering against a broken exam. MindStudio’s PIV explainer gives you two loop-back options: bad code loops back to Implement, a bad spec loops back to Plan. Useful framing! It’s also missing the third case I measured: the gate itself is wrong, and neither loop-back can save you because the agent fixing the code can’t touch the thing judging it.

Here are four of the fifteen, the ones that burned me hardest:

Failure mode Symptom Fix
Whole-repo lint 924 errors across 898 files, all older than the run; burned all 8 iterations Lint only touched files (the one passing run scoped to its three files)
Hardcoded port 3000 Another app held it; gate tested the wrong site; 38 findings belonging to a different site Configurable port or config check
“Exactly one new migration” True on the iteration that creates it, false on every one after Check state, not one-time transitions
bash-4 idioms (mapfile) Aborts on macOS bash 3.2 Write for the bash the team actually runs

The whole-repo lint is my favorite, by which I mean the one that made me consider a career change. Three runs died the exact same way. The linter reported 924 errors across 898 files, every single one pre-existing, while the run’s own files were clean. All 8 iterations spent. A gate that punishes the codebase’s history is not validating your agent, it’s hazing it.

And look, MindStudio’s “back to Plan” option doesn’t cover any of this either. You can’t replan your way out of a mapfile call.

The full failure-to-guard map:

Failure What happened Guard Rule
Production write from inside a run payload migrate hit the live database, landed in the production migration journal Per-run empty Postgres; gate exports its URL; hook refuses production-host migrations unattended Each run gets its own database
Green gate, work undone 3 of 10 tasks never written Read SKIPPED lines; list files each task creates The exit code is not the task list
Gate that can never pass 15 ways a generated gate broke itself Lint only touched files; keep a real build; read the gate first Gate is code; review it first

The last column is the whole article, honestly. Gate is code. Review it first.

How Our Loop Compares to Other Workflows and the Rules We Kept

So how does my loop stack up against everyone else’s? Because at this point you’ve got options, and I’ve read most of them, some out of research and some out of spite.

The most rigorous competitor is tworkflow, which runs Context → Plan → Implement → Review → QA → Ship → Retro. Seven phases, two Claude Code skills, and a “40% context rule,” with every claim traced back to official docs, which is a level of citation hygiene I respect deeply and do not practice. Their loop adds dedicated Review and QA phases where mine has a single gate. That’s genuinely better structure! But here’s my problem: nothing in their loop audits the gate itself. Review the code, QA the output, ship, retro. Nobody is reviewing the judge. My loop adds exactly one thing theirs doesn’t have, and it’s the gate audit, because I got burned by a gate three separate ways and now I read validate.sh like it owes me money.

Then there’s Damian Galarza’s setup, which pulls context from Linear issues, Obsidian notes, and Sentry exceptions before implementation even starts. His human approval checkpoint after planning is, and I want to be clear about this, the cheapest guard on the entire market. It costs zero seconds. A human reads the plan before the agent touches code. Free. Beautiful. It also does not catch gate bugs, because the plan can be immaculate and validate.sh can still be checking port 3000 on the wrong website. His guard and mine are not competitors. Run both.

And the honest neighbors: Aman’s primer formalizes the loop exactly as I run it, inspect, plan, execute, validate, iterate, and treats validation as tests and builds, full stop. The developersvoice end-to-end SDLC post does the same. Neither is wrong. Neither asks the question I’m asking, which is: trustworthy verifier, or verifier that happens to exit zero? That’s the whole gap. Everyone describes the loop’s success path. I’m describing its failure path, with timestamps.

The rulebook we kept

So here’s the consolidated rulebook, paid for in production incidents and burned iterations:

  1. Each run gets its own database. Empty Postgres container, 12.8–14.7 s to start, 0.9 s to remove. Non-negotiable after the production migration.
  2. The exit code is not the task list. Read the SKIPPED lines. Confirm every file each task creates exists. Two minutes.
  3. Gate is code; review it first. Before the long run, not after it burns all 8 iterations on a gate that could never pass.
  4. Lint only touched files. 924 pre-existing errors is hazing, not validation.
  5. Keep a real build. My green run skipped the build and shipped a third of nothing.

That’s it. Five rules, two of them free, all of them earned the embarrassing way. I could be completely wrong about some of the finer points here, but I’m extremely confident about the five.

Close: Trust the Receipt, Not the Checkmark

We’ve reached the end, so let me give you the thesis one more time, because it’s the only part I need you to actually remember: the loop works, and the gate it trusts is where it fails. That’s the whole article. Everything else is receipts, and I’ve written up the detailed incident versions over on my Fixes page if you want what broke, the mechanism behind it, and the repair, without the sermon.

And I know how this sounds. I’m the person who had a run write to production twice, ship a green checkmark over a third of nothing, and burn eight iterations on a gate that could never pass. My credibility on workflow hygiene is, let’s say, earned the expensive way. Am I the right messenger? Probably not. Am I the right messenger? Absolutely, because I paid for these lessons and I’d like you to get them at a discount. The step-by-step versions of the setups that survived all this, including the per-run database build, live in my Workshops section, and I built each one specifically so you don’t repeat my exact mistakes, because frankly, someone should profit from my suffering and it might as well be you.

Here’s the part I find genuinely interesting. From inside the loop, the gate looks authoritative. Exit zero, move on. The one component nobody audits is the one component everyone trusts, and that’s exactly why rule three exists: gate is code; review it first. I could be completely wrong here, and maybe future models will read validate.sh with the suspicion it deserves, but I’m not betting my production database on it. Again.

So before your first long unattended run, here’s the checklist. Not vibes. Checklist.

  1. Read the gate. Before the run, not after it dies. Gate is code; review it first.
  2. Read the SKIPPED lines. Every one. If the gate skipped the build because an env var was unset, that’s not a pass, that’s a shrug.
  3. List the files each task creates and confirm they exist. Two minutes. My green run was missing five of ten HTTP routes and the gate never noticed because nothing checked for existence.
  4. Confirm each run has its own database. 12.8–14.7 seconds to start, 0.9 to remove. The cheapest insurance in this entire writeup, and it’s the difference between a test journal and production.

Is this pedantic? Maybe. Do you know what’s more pedantic? Explaining to your team why an unmerged branch created a table in your live database. I’ve done it. It’s not a fun meeting.

If you’re anything like me, you want the loop to be the hero. It’s not. The gate is the hero, and it’s the one most likely to lie.

So trust the receipt, not the checkmark. A green gate is a claim. It is not a receipt.

Sources

Questions, answered

What is the prp-piv loop in a Claude Code workflow?
It's a four-step loop that runs in Archon: Research reads the codebase, Plan writes the task list and the validate.sh gate script, Implement writes code, and Validate runs the gate. A red gate sends the run back to implement, up to 8 iterations.
Why can a green validation gate be misleading?
In one run the gate finished green while 3 of 10 tasks were never written, including 5 of 10 HTTP routes, because it checked lint, types and tests but never verified files exist, and it silently skipped the build and a test level due to unset environment variables. The loop's exit signal is the gate's exit code, not the task list.
How do I stop agent runs from touching my production database?
Give every run its own empty Postgres container and export its URL before the gate runs, since Bun and Next's env loader won't override an already-set variable. Measured cost is 12.8–14.7 seconds to start and 0.9 seconds to remove, with a 72-second migration chain per run.
What are the key rules for auditing a Claude Code validation gate?
Read validate.sh before the run starts, read every SKIPPED line, and confirm each file the tasks say they create actually exists. Also lint only touched files and keep a real build, since a whole-repo lint burned all 8 iterations on 924 pre-existing errors.

Keep reading

The column · weekly · free

The next piece, the morning it is published.

One written column a week. The email service holds the list.

Luke Sidney writes and builds everything here, alone, in the open. lukesidney.me