A pairing session with a senior engineer usually ends with one recorded decision and a reproduction you can run again, not with a clever patch. The session described here started from a generated ingestion worker that ordered records correctly on a laptop and incorrectly on a preview server, and it finished with a one-line invariant plus a decision record that survived the next three model prompts. Every artifact below runs on a stock Node.js install, so the same session can be repeated without private infrastructure.
The starting point: one generated patch, two environments
The worker was drafted by a free model inside MonkeyCode and then run unchanged on the free server option, which is how the environment difference became visible at all. Disclosure: This article was prepared as part of MonkeyCode's product outreach. The operator describes the free tier as including a token allowance advertised at ten million tokens and a free server option, and those figures along with the available capacity change over time, so confirm the current terms on the project page before planning around them.
The worker read a directory of CSV fixtures, normalized each row into a record, sorted the records by a display name, and printed newline-delimited JSON to stdout. It passed on the laptop and failed on the server, and the failure was not a crash: the same seventy rows arrived in a different order starting at line four. That is the hardest kind of difference to debug, because both runs exit zero and both outputs look entirely plausible.
Step 1 - Capture three facts before anyone discusses a fix
Three artifacts make the conversation concrete: the digest of the input, the exit code, and the digest of the error stream. Together they replace the phrase "it works on my machine" with a comparison of two byte strings, which a senior engineer can reason about in seconds.
# 1. Digest the bytes the process actually read, not the path that was typed.
find fixtures -type f -print0 | sort -z | xargs -0 shasum -a 256 | shasum -a 256
# 2. Keep stdout, stderr, and the exit code in separate places.
LC_ALL=C node src/ingest.js fixtures > out.C.txt 2> err.C.txt; echo "exit=$?"
# 3. Digest both streams so the next diff is cheap to describe.
shasum -a 256 out.C.txt err.C.txt
The numbered protocol that came out of the session reads as follows.
- Restate the failing behaviour in one sentence and attach the input digest to that sentence.
- Name the last known-good run with its command, its digest, and its environment variables.
- Compare the two output digests before reading a single line of the generated code.
- Postpone the fix until the failure repeats on demand with one command and one fixture set.
Step 2 - The four questions that moved the session
The first question was which input the failing run actually read, answered with a digest rather than a file path. The second asked what had changed between the passing run and the failing one, with the constraint that only one variable could be named. The third asked whether the failure would survive if the fixture were halved, which is delta debugging compressed into a single sentence. The fourth asked what evidence would cause the decision to be reversed next week, which forces a written reversal condition instead of an implicit belief.
Those four questions cost roughly ten minutes and eliminated two of the three hypotheses that had been circulating. The remaining hypothesis was that record ordering depended on the host environment, and the session had a way to test that immediately.
Step 3 - The three dead ends
Each dead end consumed real time and produced no movement, and each one is recorded here so the next session can skip it.
| Dead end | Effort | Evidence it failed | Root cause |
|---|---|---|---|
| Re-prompt the free model with a longer context window | 25 min | Output digest changed at four lines, mismatch unchanged | The flaw sat in the environment contract, not in the generated logic |
| Add retries plus a two-second sleep around the run | 15 min | Failure disappeared twice, then returned as a different diff | Timing masked the ordering rather than fixing it |
| Pin the server locale to match the laptop | 20 min | Server passed and the laptop failed with a second digest | Moving a disagreement between hosts is not a fix |
The pattern behind all three is identical: each intervention changed the observable surface without changing the underlying contract. A senior pairing partner tends to name that pattern early, because it is visible from the evidence rather than from the code itself.
Step 4 - The one decision that was kept
The root cause was locale-dependent collation. The Intl.Collator constructor and String.prototype.localeCompare both resolve a default locale from the runtime environment, and Node.js reads that environment at startup, so a server started with LANG=C and a laptop started with LANG=en_US.UTF-8 can order the same list differently. The default Array.prototype.sort() comparator carries its own separate problem, because it compares UTF-16 code units and produces an order that no human reader expects for accented names.
The decision kept from the session was a single invariant with two clauses, and it was committed as code rather than as a comment.
// src/order.js
// One collator for the whole process. The locale is explicit and never inherited.
const COLLATOR = new Intl.Collator("en", {
usage: "sort",
sensitivity: "variant",
numeric: true,
});
// Identity comparisons use one normalization form so two spellings cannot drift apart.
export function normalizeKey(value) {
return value.normalize("NFC");
}
export function sortRecords(records) {
return [...records].sort((a, b) =>
COLLATOR.compare(normalizeKey(a.name), normalizeKey(b.name)),
);
}
The reversal condition was written down at the same moment: revisit the decision if the pipeline later accepts per-tenant collation rules or binary payloads, because one fixed locale would then be the wrong default.
Step 5 - The replay harness that enforces the decision
A decision that only lives in a chat thread disappears at the next prompt, so the session ended with a script that fails loudly when the invariant breaks. The harness runs the same command under two locales and compares output digests instead of comparing output text.
#!/usr/bin/env bash
# replay.sh - run one generated patch against frozen fixtures under two locales.
set -euo pipefail
FIXTURES="${1:-fixtures}"
RUNNER="${RUNNER:-node}"
for locale in C en_US.UTF-8; do
LC_ALL="$locale" "$RUNNER" src/ingest.js "$FIXTURES" > "out.$locale.txt" 2> "err.$locale.txt"
echo "locale=$locale out=$(shasum -a 256 "out.$locale.txt" | cut -d' ' -f1)"
done
if cmp -s out.C.txt out.en_US.UTF-8.txt; then
echo "OK: ordering is locale-independent"
else
echo "FAIL: locale changed the output"
diff -u out.C.txt out.en_US.UTF-8.txt | head -40
exit 1
fi
The harness is deliberately narrow, because it tests exactly one property, takes about two seconds, and can run on the free server option before the patch is promoted to a preview environment.
Step 6 - Record the decision before the next prompt
The final artifact is the decision record, stored as one JSON object per line so it can be grepped and diffed like source code. The rule adopted in the session is simple: no second prompt to the model until the first decision is written down.
{"id":"D-07","date":"2026-10-13","question":"Why does the same fixture order differently on the server?","evidence":"out.C.txt differs from out.en_US.UTF-8.txt, first divergence at line 4","dead_ends":["re-prompt with more context","retry with sleep 2","pin the host locale"],"decision":"Normalize identity keys to NFC at ingestion, and build all user-visible ordering from one Intl.Collator with an explicit locale.","reversal":"Revisit if tenants need per-locale collation or the pipeline accepts binary payloads."}
Limitations and who should skip this
The workflow fits small, deterministic pipelines where a failure reproduces from a frozen input set, and it fits teams that already review generated patches before merging them. It is a poor fit in several specific situations.
- Non-deterministic failures such as network, clock, or concurrency problems need a different harness, because digesting output will not isolate them.
- One-off scripts and throwaway notebooks rarely justify the overhead of a decision record and a two-locale replay.
- Free or shared servers are convenient for reproduction, but they are not a production target, so secrets and real user data must stay off them.
- The single-collator rule is wrong for multi-tenant products where ordering must follow each caller's locale.
- Teams under strict reproducibility requirements should host the runner themselves and pin the runtime image rather than depend on a shared free tier.
The free model access and free server option in MonkeyCode are enough to reproduce the failing run and to keep the replay script in place, and the decision record costs nothing to maintain.
Top comments (0)