Dev.to WebDev πŸ›  Dev πŸ‘ 0 πŸ“– 9 min read

The Senior Kept the Failing Curl After Three Model Dead Ends

The junior already had a chat window open when the staging webhook went red. A single integration test had failed after a header rename on the producer side. The senior did not look at the suggested rewrite. The laptop s

The junior already had a chat window open when the staging webhook went red. A single integration test had failed after a header rename on the producer side. The senior did not look at the suggested rewrite. The laptop stayed on the failing terminal, and the pairing session started with the command that had just printed 401.

This walkthrough reconstructs that session as a method, not as a production postmortem. Names of services are fixtures. The keep decision at the end is the only part that survived the afternoon.

The freeze before any remote token moved

The junior wanted to paste the handler, the test, and the CI log into a remote coding model. The senior refused that bundle. A model that never saw the failing command cannot be scored against it later. The first artifact was therefore a frozen shell line, copied verbatim from the terminal, not from memory.

curl -sS -D - -o /tmp/webhook-body.txt \
  -X POST 'http://127.0.0.1:8787/webhooks/orders' \
  -H 'content-type: application/json' \
  -H 'x-webhook-signature: sha256=deadbeef' \
  --data-binary @fixtures/order-created.json

The response line that mattered was short.

HTTP/1.1 401 Unauthorized

The fixture body was also frozen. No pretty-print. No re-encode. The senior wrote the sha256 of fixtures/order-created.json on a sticky note so later diffs could not quietly change the payload.

shasum -a 256 fixtures/order-created.json

Only after that freeze did the session admit a third voice: a free remote coding model running on a free server. Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode is an open-source project that offers free model access and a free server option. Those two availability claims are the only product facts used below. No model name, quota, latency number, or hardware story is assumed.

The model was treated as a noisy pair, not as the author of the patch.

Four questions the senior asked out loud

The junior still wanted to dump the whole ordersWebhook.js file. The senior asked four questions instead, and wrote the answers on the pairing log before any prompt left the room.

  1. What exact command is red, and what status does it print today?
  2. Which header name does the producer send after the rename, as proven by a captured request, not by a blog post?
  3. Which single function is allowed to change if the command is going to turn green?
  4. What must stay byte-identical: the fixture body, the route, or both?

The answers were boring, which was the point.

  • Command: the curl above, still 401.
  • Header: x-webhook-signature, confirmed from the producer’s latest capture, not x-hub-signature-256.
  • Function: verifyOrderSignature only.
  • Byte-identical: fixtures/order-created.json and the route string /webhooks/orders.

The prompt to the model was then a slice, not a novel. The frozen curl, the four answers, the current function, and a ban on new dependencies went into the request. Everything else stayed on disk.

// current slice β€” labeled example, not a library
import { createHmac, timingSafeEqual } from 'node:crypto';

export function verifyOrderSignature(rawBody, header, secret) {
  if (typeof header !== 'string' || !header.startsWith('sha256=')) {
    return false;
  }
  const sent = Buffer.from(header.slice('sha256='.length), 'hex');
  const expected = createHmac('sha256', secret).update(rawBody).digest();
  if (sent.length !== expected.length) {
    return false;
  }
  return timingSafeEqual(sent, expected);
}

Dead end one: a confident full rewrite

The first model reply replaced the handler, the router, and the test. It introduced a WebhookService class and a retry queue. The junior almost applied it. The senior scored the diff against question three and killed it in one pass.

The rewrite never ran the frozen curl. It also renamed the header in the test to match the model’s memory of another vendor. That is a dead end with a name: scope explosion. A pairing session that scores β€œdoes this look like production code” will keep it. A session that scores β€œdoes the same curl change color” will not.

The kill note in the log was one line.

KILL rewrite: touches router + test header + new class; frozen curl never executed

Dead end two: an invented header

The second reply stayed inside verifyOrderSignature. That looked better. It still failed the producer capture. The model checked x-hub-signature-256 and documented the choice as β€œstandard.” The captured traffic used x-webhook-signature. Standard was not the contract.

The senior did not argue with the model. The junior ran a one-line assertion against the capture file they already had.

python3 - <<'PY'
import json,sys
cap=json.load(open('captures/producer-2026-09-17.json'))
print(cap['headers'].get('x-webhook-signature','MISSING'))
print(cap['headers'].get('x-hub-signature-256','ABSENT'))
PY

x-hub-signature-256 was absent. The suggestion was killed for inventing an input the producer does not send. Hallucinated contract is the second dead end. Free models do this often when the prompt omits a captured header dump. The fix is not a longer lecture in the system prompt. The fix is to keep the capture on the pairing desk and refuse any patch that names a header the capture does not contain.

Dead end three: a package that is not in the lockfile

The third reply added fast-stable-stringify so the HMAC would hash β€œcanonical JSON.” The handler currently signs the raw body. The producer signs the raw body. Canonicalization would have made a green unit test and a red curl.

The senior opened package-lock.json rather than the README.

node -e "const l=require('./package-lock.json'); console.log('fast-stable-stringify' in (l.packages||{}) || 'fast-stable-stringify' in (l.dependencies||{}))"

The package was not there. Adding it would have changed the lockfile, the Docker layer cache, and the signed bytes. Unsigned dependency is the third dead end. The pairing rule was already on the wall: no new packages unless the frozen command cannot be made green without them. This command could.

The keep decision

After three kills, the junior wrote a four-line change by hand, using the model only as a rubber duck for the hex-length check. The patch stayed inside verifyOrderSignature. It stopped parsing the signature as utf8 and started parsing it as hex. The fixture body did not move. The route did not move. The frozen curl was the only scorer.

# same command as the freeze β€” expected: HTTP/1.1 200
curl -sS -D - -o /tmp/webhook-body.txt \
  -X POST 'http://127.0.0.1:8787/webhooks/orders' \
  -H 'content-type: application/json' \
  -H "x-webhook-signature: sha256=$(python3 - <<'PY'
import hmac,hashlib,os
secret=os.environ['WEBHOOK_SECRET'].encode()
body=open('fixtures/order-created.json','rb').read()
print(hmac.new(secret, body, hashlib.sha256).hexdigest())
PY
)" \
  --data-binary @fixtures/order-created.json

The keep rule the session wrote down was stricter than β€œthe test passed.”

  • Keep a patch only if the same curl flips from 401 to 200.
  • Kill a patch that changes the fixture, the route, the header name, or package-lock.json.
  • Kill a patch that the pairing log cannot map to one of the four questions.
  • Keep the failing curl in the log even after it turns green, so a later model session cannot replace the scorer.

That keep decision is the reusable artifact. The model was optional. The freeze was not.

Artifact: a pairing keep-gate the desk can actually run

The log is JSON because JSON diffs in review. The gate is a small Node script because the account’s pairing desks already run Node. Both are labeled examples. They were not executed against a vendor API for this article.

{
  "frozenCommand": "curl -sS -D - -o /tmp/webhook-body.txt -X POST 'http://127.0.0.1:8787/webhooks/orders' ...",
  "frozenStatus": 401,
  "captureHeader": "x-webhook-signature",
  "allowedFiles": ["lib/verifyOrderSignature.js"],
  "questions": [
    "What exact command is red?",
    "Which header does the producer send?",
    "Which single function may change?",
    "What must stay byte-identical?"
  ],
  "turns": [
    {"id": 1, "verdict": "kill", "reason": "scope explosion"},
    {"id": 2, "verdict": "kill", "reason": "invented header"},
    {"id": 3, "verdict": "kill", "reason": "unsigned dependency"},
    {"id": 4, "verdict": "keep", "reason": "same curl now 200; only verifyOrderSignature changed"}
  ]
}
// pairing-keep-gate.mjs β€” example gate, run locally against a log file
import { readFileSync } from 'node:fs';

const log = JSON.parse(readFileSync(process.argv[2], 'utf8'));
const errors = [];

if (!log.frozenCommand || typeof log.frozenStatus !== 'number') {
  errors.push('missing freeze');
}
if (!Array.isArray(log.questions) || log.questions.length < 4) {
  errors.push('need four written questions before any model turn');
}
const keeps = (log.turns || []).filter((t) => t.verdict === 'keep');
const kills = (log.turns || []).filter((t) => t.verdict === 'kill');
if (keeps.length !== 1) errors.push('exactly one keep decision required');
if (kills.length < 1) errors.push('a keep with zero kills usually means the freeze was skipped');
for (const t of log.turns || []) {
  if (!t.reason) errors.push(`turn ${t.id} missing reason`);
}

if (errors.length) {
  console.error(errors.join('\n'));
  process.exit(1);
}
console.log('pairing keep-gate: ok');
node pairing-keep-gate.mjs pairing-log.json

The script does not call a model. That is deliberate. A gate that lives inside the chat window dies with the window. A gate that lives next to the fixture can be run in CI as a process check: no merge if the log has a keep without a freeze.

Decision table used on the desk

Signal on the diff Question it fails Verdict
New class, router, or test file Which single function may change? Kill
Header name not in the capture Which header does the producer send? Kill
New package or lockfile edit What must stay byte-identical? Kill
Fixture or route bytes change What must stay byte-identical? Kill
Same curl, status flips, one function What exact command is red? Keep

Where a free model and a free server actually fit

A pairing desk still needs a place to send the slice when the office GPU is busy and the laptop cannot host a local coder. Free model access plus a free server option covers that gap without pretending the remote voice is a teammate. The workflow above does not depend on a particular product remaining free, remaining fast, or remaining available. If the remote side is down, the freeze, the four questions, and the keep-gate still work. The model is a cheap extra pair for generating candidates. The senior still owns the kill list.

Use the remote side only after the freeze. Send the slice, not the repository. Paste the kill reasons from earlier turns into the next prompt so the model does not regenerate dead end one as dead end four.

Limitations

The keep-gate does not detect a wrong secret that happens to be shared between test and staging. It does not prove timing safety beyond the presence of timingSafeEqual. It does not replace a contract test against the real producer. It also does not stop a determined junior from editing pairing-log.json to invent a freeze. Reviewers still read the curl.

Captured headers go stale. A keep decision from Tuesday is invalid on Thursday if the producer ships another rename. The log should carry the capture filename and a checksum, not a vibe.

Who should not use this approach

  • Desks that cannot reproduce the failing command locally or on a throwaway server should not invite a remote model first. They need a repro, not a diff.
  • Teams whose policy forbids sending even a sliced handler to a hosted model should run the same freeze and questions without the third voice.
  • Incidents that involve live customer payloads do not belong in a free remote prompt. Redact or synthesize the fixture first.
  • Greenfield spikes with no failing command have nothing to freeze. This method is for a red line that already exists.

The junior closed the chat window after the keep. The failing curl stayed in the log, now printing 200. The three dead ends stayed too. That record is the part of the pairing session worth keeping. If a desk wants a remote model for the next slice without standing up GPU hardware first, MonkeyCode’s free model access and free server option is one place to run the same keep-gate against a noisy third voice.

πŸ“° Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.