DEV Community

Sam Chen
Sam Chen

Posted on

Two Sessions, One Repo: Four Drift Anti-Patterns and a Preflight Script

The drift between your AI session and your second box is a contract problem.

It is not a model problem. It is not a luck problem. It is a missing file.

You asked for code in one window. It ran there, so you shipped it. Then it met a different host and quietly fell over.

Who is surprised by that? Only someone who has not done it twice yet.

Here are four anti-patterns I keep seeing, with the replacement pattern for each. Then comes the script I actually run.

Anti-pattern 1: The Chat-Only Spec

Symptom: You cannot rebuild the change without scrolling a transcript.

The instructions lived in a conversation. The repo has no record of the goal, the constraints, or the stop condition.

A week later you ask the same assistant for a fix. It guesses at your conventions, because it has nothing to read.

Root cause: The spec was treated as conversation, not as an artifact. Conversation is ephemeral and per-window. A repo is neither.

Replacement: Put the contract in the repo as a file. Keep it short and boring.

<!-- CONTRACT.md -->
# Task contract
Goal: add /healthz returning 200 with a JSON body
Constraints:
- no new runtime dependencies
- do not touch src/billing/**
Stop when: tests pass and diff is under 200 lines
Verify with: npm test && ./preflight.sh
Enter fullscreen mode Exit fullscreen mode

Now every session reads the same file. The stop condition is visible to you too, which matters more.

Is the constraint list longer than five items? Then the task is too big. Split it.

Anti-pattern 2: The Inherited Runtime

Symptom: Works locally, dies on the second box with a syntax or API error.

Your laptop has the runtime you installed months ago. The other host has whatever its image shipped with.

Root cause: The runtime version was never written down, so it was never checked. Nobody can diff a value that does not exist.

Replacement: Pin it in a one-line file and assert it at startup.

# .runtime-version
v22.11.0
Enter fullscreen mode Exit fullscreen mode
# assert-runtime.sh
want="$(cat .runtime-version)"
have="$(node --version)"
if [ "$want" != "$have" ]; then
  echo "runtime drift: want $want, have $have" >&2
  exit 1
fi
Enter fullscreen mode Exit fullscreen mode

Call the assertion from your start script, not from your memory. Memory is the thing under test here.

Anti-pattern 3: The Unwritten Environment

Symptom: Boot fails with an undefined key, or worse, silently uses a default.

Your .env has eleven keys. The other box has nine. Nobody knows which two are missing until something 500s.

Root cause: The set of required keys was never declared. Absence is invisible without a checklist.

Replacement: Commit a sample file with keys and empty values. Check every key on boot.

# .env.sample (committed)
DATABASE_URL=
HEALTH_PORT=8080
Enter fullscreen mode Exit fullscreen mode
# every key listed in .env.sample must exist in the environment
missing=0
while IFS= read -r line; do
  key="${line%%=*}"
  case "$key" in ''|\#*) continue ;; esac
  if [ -z "${!key:-}" ]; then
    echo "missing env key: $key" >&2
    missing=1
  fi
done < .env.sample
exit "$missing"
Enter fullscreen mode Exit fullscreen mode

Remember to export before checking: set -a; . ./.env; set +a.

A missing key should stop the boot. A default value should be a decision, not an accident.

Anti-pattern 4: The Unbounded Diff

Symptom: The change touches files you never asked about, and you approve it anyway.

An assistant with no stop condition keeps improving things. Formatting drifts. A helper gets "cleaned up." Review becomes archaeology.

Root cause: There is no review budget, so there is no signal that the task escaped its box.

Replacement: Set a diff budget and fail loudly when it is exceeded.

total="$(git diff --numstat | awk '{a+=$1; d+=$2} END {print a+d+0}')"
budget="${DIFF_BUDGET:-200}"
if [ "$total" -gt "$budget" ]; then
  echo "diff is $total lines, budget is $budget: split the task" >&2
  exit 1
fi
Enter fullscreen mode Exit fullscreen mode

Exceeding the budget is not a failure of the model. It is a signal that your contract was vague.

The artifact: one preflight script, run on two boxes

Here is the whole thing. It is plain bash and has no dependencies beyond git and curl.

#!/usr/bin/env bash
# preflight.sh - run before you accept any change, on every box.
set -uo pipefail

fail=0
say() { printf '%-4s %s\n' "$1" "$2"; }

# 1. Runtime matches the pin.
want="$(cat .runtime-version 2>/dev/null || echo missing)"
have="$(node --version 2>/dev/null || echo none)"
if [ "$want" = "missing" ]; then
  say FAIL "no .runtime-version file"; fail=1
elif [ "$want" != "$have" ]; then
  say FAIL "runtime drift: want $want, have $have"; fail=1
else
  say OK "runtime $have"
fi

# 2. Every declared env key is present.
if [ -f .env.sample ]; then
  while IFS= read -r line; do
    key="${line%%=*}"
    case "$key" in ''|\#*) continue ;; esac
    if [ -z "${!key:-}" ]; then
      say FAIL "env key not set: $key"; fail=1
    fi
  done < .env.sample
fi

# 3. The service answers on the box you think you are on.
url="${HEALTH_URL:-http://127.0.0.1:8080/healthz}"
code="$(curl -s -o /dev/null -w '%{http_code}' --max-time 5 "$url" || echo 000)"
if [ "$code" = "200" ]; then
  say OK "health $url"
else
  say FAIL "health $url -> $code"; fail=1
fi

# 4. The change is small enough to read.
total="$(git diff --numstat | awk '{a+=$1; d+=$2} END {print a+d+0}')"
if [ "$total" -gt "${DIFF_BUDGET:-200}" ]; then
  say FAIL "diff is $total lines; split the task"; fail=1
else
  say OK "diff $total lines"
fi

# 5. Print a receipt you can compare across boxes.
say OK "host $(hostname) | $(date -u +%FT%TZ)"
if [ "$fail" -eq 0 ]; then say OK "preflight passed"; else say FAIL "preflight blocked"; fi
exit "$fail"
Enter fullscreen mode Exit fullscreen mode

Run it like this:

set -a; . ./.env; set +a
chmod +x preflight.sh
./preflight.sh
Enter fullscreen mode Exit fullscreen mode

Then run it on the other box, with the same commit checked out. Compare the receipts.

Same runtime, same env keys, same health answer, same budget verdict? The two boxes agree. That is all a contract is.

On the second host, hostname differs by design. Everything else should match. When it does not, you found the drift before a user did.

Where the second box comes from

This is the part of my workflow where a free server option is genuinely useful. I need a second, cheap host to run the same script against, and I do not want it on my laptop.

MonkeyCode bundles free model access and a free server option. I use the free models for the small mechanical changes, and the free server as the second box in the receipt comparison. That is the whole role it plays here.

Disclosure: This article was prepared as part of MonkeyCode's product outreach.

One honest caveat: free tiers are offered as-is by whoever runs them. Do not treat any free host as permanent infrastructure. Do not assume it keeps your data, your uptime, or your configuration.

A decision table for what belongs where

Work type Laptop Second box Notes
Prompt drafting, renames, test scaffolds Yes No Cheap and revertible
Environment parity check Yes Yes This is the point of the second box
Anything touching secrets or customer data Yes No Keep it off shared hosts
Long-running jobs, queues, cron No No Use a host you actually control
Production traffic No No Nothing here is a prod story

Print the table in your repo if your team argues about this. Arguing about a table is cheaper than arguing about an outage.

What the script will not catch

Be clear about the limits. This is a drift detector, not a test suite.

  • It does not tell you the code is correct. It tells you the boxes agree.
  • It cannot see missing data, permissions, or network policy you never declared.
  • It trusts .env.sample to be complete. A stale sample passes happily.
  • It measures line counts, not risk. A two-line change can still ruin your week.

So keep real tests. Keep a rollback. Keep a human who reads the diff.

Who should skip this

You should not use this pattern if any of these are true.

  1. You handle regulated or personal data and cannot use a third-party box for the check.
  2. Your workload needs GPUs, persistent storage, or long-lived background processes.
  3. Your team already has staging parity and CI that runs the same assertions per commit.
  4. You cannot pin a runtime because the platform controls the image.

For those cases, keep the four replacement patterns and drop the free host. The contract file and the budget check cost nothing.

The short version

Two sessions drift because nothing forces them to agree.

So write the contract down. Pin the runtime. Declare the env keys. Cap the diff. Run the same script on both boxes and compare receipts.

If you want a second box without a credit card, the free server option is the cheapest way I have found to get one. Just keep the receipt, and read it before you merge.

Top comments (0)