The first fifteen minutes of an AI cache fix should replay headers, not invent another middleware file. I do not want a confident story about stale SPAs; I want a disposable host that repeats one request twice. If the second response still looks haunted, the patch is fiction, and I should stop typing. Why would I merge a Cache-Control lecture that never touched the wire in this brownfield app?
Brownfield front ends fail in the quiet places, like service workers and intermediary caches that tutorials skip. A free coding model will invent ETags, purge keys, and a casual no-store as if your CDN were a textbook diagram. Have you watched a generated patch rename a header that your origin never sent on any environment? That is the DX break: you spend the opening quarter hour arguing with prose while the browser serves yesterday.
The one fix that mattered was embarrassingly small, and it did not live inside the prompt window at all. I wrote a replay probe that hits the same URL twice, prints the cache headers, and diffs the two copies. Until those two copies agree with reality, I refuse to discuss architecture with the model in any serious way. Does that sound slow? It is faster than reverting a service worker that gaslit every teammate.
I still needed two cheap ingredients: a model that could draft a patch, and a server I could throw away after the replay. Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode's free model access and free server option are relevant here only as a draft-and-replay pair I can abandon. The rest of this workflow is local, boring, and useful even if you never mention a product name again.
The probe is ordinary Node, because I already have it, and I do not want another package manager argument. It records status, cache-control, etag, age, last-modified, vary, and a simple boolean for whether set-cookie appeared. Then it hits the URL again and prints a line-oriented diff that even a tired reviewer can scan. If the model invented a header, the diff stays empty on the server and loud in the generated markdown.
Save this as header-replay.mjs. It is a proposed local artifact, not a benchmark, and it uses only Node's standard http and https modules.
// header-replay.mjs — proposed workflow, run against URLs you already may fetch.
import http from 'node:http';
import https from 'node:https';
import { URL } from 'node:url';
const KEYS = [
'cache-control',
'etag',
'age',
'last-modified',
'vary',
'expires',
'pragma',
'surrogate-control',
'cdn-cache-control',
];
function fetchOnce(raw) {
const u = new URL(raw);
const lib = u.protocol === 'https:' ? https : http;
return new Promise((resolve, reject) => {
const req = lib.request(
{
hostname: u.hostname,
port: u.port || (u.protocol === 'https:' ? 443 : 80),
path: `${u.pathname}${u.search}`,
method: 'GET',
headers: {
'user-agent': 'header-replay/0.1',
accept: 'text/html,application/json;q=0.9,*/*;q=0.8',
},
},
(res) => {
const headers = {};
for (const k of KEYS) headers[k] = res.headers[k] ?? '';
headers.status = String(res.statusCode);
headers['set-cookie?'] = Array.isArray(res.headers['set-cookie']) ? 'yes' : 'no';
res.resume();
res.on('end', () => resolve(headers));
},
);
req.on('error', reject);
req.end();
});
}
function diff(a, b) {
const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
const lines = [];
for (const k of keys) {
if (a[k] !== b[k]) lines.push(`${k}: ${JSON.stringify(a[k])} -> ${JSON.stringify(b[k])}`);
}
return lines;
}
const url = process.argv[2];
if (!url) {
console.error('usage: node header-replay.mjs <url>');
process.exit(2);
}
const first = await fetchOnce(url);
const second = await fetchOnce(url);
console.log('pass-1', first);
console.log('pass-2', second);
const changed = diff(first, second);
console.log(changed.length ? 'UNSTABLE_HEADERS' : 'STABLE_HEADERS');
for (const line of changed) console.log(line);
I run that before I paste any model output into the repo, because silence on the wire is the actual product bug. The first fifteen minutes on my clock look like a short ritual, not a brainstorming session with a chatbot. Can you feel how different that is from opening a chat and asking for a cache architecture? The probe answers one question: did this URL even speak HTTP the way the patch pretends?
# Minute 0–3: prove you can see the brownfield URL at all.
node header-replay.mjs https://staging.example.com/app
# Minute 3–6: capture a baseline you can diff later, nothing fancy.
node header-replay.mjs https://staging.example.com/app > /tmp/baseline-headers.txt
# Minute 6–10: if you have a throwaway host, point the same probe at it.
node header-replay.mjs http://127.0.0.1:8787/app > /tmp/replay-headers.txt
# Minute 10–15: only now ask a free model for a patch, and feed it the two dumps.
diff -u /tmp/baseline-headers.txt /tmp/replay-headers.txt || true
I keep the model on a leash by pasting those dumps and asking for a patch that names only headers that already appeared. If the draft mentions Surrogate-Control and the baseline never did, I throw the draft away without negotiating. Is that rude to the model? Maybe, but rudeness is cheaper than a service worker that pins a broken bundle to every laptop on the team. The disposable server matters because I can apply the patch there, rerun the probe, and delete the machine before the story spreads.
Here is the prompt shape I actually paste after the dumps, labeled as a proposal you should edit for your stack. I want the model to argue with the wire, not with my anxiety about freshness.
You are reviewing a brownfield SPA cache issue.
Only discuss headers that appear in pass-1 or pass-2 below.
If you need a header that is missing, say UNPROVEN and stop.
Do not invent a CDN product, a purge API, or a service worker file.
Return: (1) which header from the dump is the likely culprit,
(2) a minimal patch against files I named, (3) the exact
header-replay command I should rerun after the patch.
pass-1 and pass-2:
<paste dumps>
Notice what this does to the first quarter hour as a developer experience. I am not pairing with a genius; I am pairing with a suspect who must point at evidence I already collected. The free model can still be wrong, and the free server can still be unlike production, which is exactly why both have to be throwaway. Would I let that suspect rewrite sw.js before the probe printed STABLE_HEADERS? Not on a weekday, and not on a brownfield app with real users.
Illustrative output, not a measurement from a private run, looks like this when the origin is merely inconsistent between two GETs.
pass-1 {
status: '200',
'cache-control': 'public, max-age=60',
etag: 'W/"9f3"',
age: '12',
'last-modified': '',
vary: 'Accept-Encoding',
expires: '',
pragma: '',
'surrogate-control': '',
'cdn-cache-control': '',
'set-cookie?': 'no'
}
pass-2 { ... age: '0' ... }
UNSTABLE_HEADERS
age: "12" -> "0"
An unstable age is often boring and honest. An etag that flips while the body did not change is a different animal, and that is when I let the model talk. A generated private, no-store on an asset that was public, max-age=31536000 is usually a panic patch, not a diagnosis. Have you seen how often AI cache advice is just panic with extra Markdown? The probe makes that panic visible in fifteen minutes instead of two days of "hard refresh for me?"
There are limits, and they are not subtle if you have ever shipped a CDN. This workflow does not prove correctness of stale-while-revalidate, cookie-gated HTML, or a service worker that intercepts fetch outside the headers the origin sent. It also does not bless a free remote server as a replica of production TLS, geo, or cache hierarchy, so do not treat a green probe as a load-test. If you cannot legally fetch the URL, or you do not own the service worker, do not point this script at it and do not paste dumps into any model.
Who should skip this entirely? Anyone hoping the first fifteen minutes will design a caching architecture from a blank repo, because architecture is not a header diff. Anyone behind a corporate proxy that rewrites Cache-Control will get a theatrical probe and a useless argument. Anyone who needs byte-level body identity should hash the payload, which this script deliberately refuses to do so you do not confuse content drift with header drift. I would also skip it for authenticated apps unless you already have a non-secret staging cookie story, because leaking set-cookie into a chat is a self-own.
I still use the cheap loop when the pain is DX, not theory: a model drafted a patch, a teammate almost merged it, and nobody had asked the origin a second question. The first fifteen minutes are for making the wire confess. After that, you can argue about architecture with whatever free model you already had, on a server you can throw away when the confession is over. If you want that draft-and-replay pair in one place, MonkeyCode's free model access and free server option are the combination I reached for; the probe above is the part I would keep even if the product vanished tomorrow.
Top comments (0)