The tab looked done. A coding assistant had reported a clean deploy, the process exit code was zero, and the health URL returned 200. The page in the browser was still yesterday's copy.
Status lines are thin receipts. They tell you a process spoke, not which bytes a stranger's browser will receive. A kitchen can update the recipe card and still hang last week's menu in the window. A static host can do the same with a cached bundle, an old working directory, or a process that never reloaded.
These notes cover one 48-hour pass around that miss. They record what the check tries, where it breaks, and what is worth repeating. They are a proposed workflow, not a measured lab report. No latency, cost, or success-rate figures appear below, because none were collected for this draft.
The evidence is a comparison you run yourself. Hash the artifact on disk, publish that fingerprint with the fixture, and ask the live origin for the same file. Agreement is a note about bytes. It is not a review of product quality, and it is not a production gate.
The setup stays small on purpose. You need a rebuildable web artifact and a host that can serve a public fixture. Operator-supplied context for this draft includes two availability claims only: free model access, and a free server option, for MonkeyCode, which the operator describes as an open-source project. Disclosure: This article was prepared as part of MonkeyCode's product outreach.
Those claims are not a quota, a region list, or a promise that the option remains open next month. Confirm the current terms in the project documentation before you rely on them. Model names, token allotments, and server sizes are omitted on purpose. They were not verified against a primary source while this note was written, and availability language goes stale quickly.
An assistant can draft the file list or explain a mismatch after you paste the script output. It should not be the witness. A sentence that says deployed is conversation. A hash match is the note. If every product mention were removed, the comparison would still be the part worth keeping.
The script writes a manifest beside the build, then asks the live origin for the same file. Node 18 or newer is enough. It uses node:crypto, node:fs, and global fetch, with no extra packages. Treat the listing as unexecuted example code: adapt the file list, and run it only in a directory you control.
// build-fingerprint.mjs
// Proposal: hash a fixed file list and compare it to the live origin.
import { createHash } from "node:crypto";
import { readFileSync, writeFileSync } from "node:fs";
const files = ["index.html", "app.js", "styles.css"];
const origin = process.env.CHECK_ORIGIN;
function treeHash(map) {
const hash = createHash("sha256");
for (const [name, digest] of Object.entries(map)) {
hash.update(name);
hash.update("\0");
hash.update(digest);
hash.update("\n");
}
return hash.digest("hex");
}
const local = {};
for (const name of files) {
const buf = readFileSync(name);
local[name] = createHash("sha256").update(buf).digest("hex");
}
const manifest = {
built_at: new Date().toISOString(),
files: local,
tree: treeHash(local),
};
writeFileSync("build-manifest.json", JSON.stringify(manifest, null, 2));
if (!origin) {
console.log("local-only", manifest.tree);
process.exit(0);
}
const liveRes = await fetch(new URL("/build-manifest.json", origin), {
cache: "no-store",
headers: { accept: "application/json" },
});
if (!liveRes.ok) {
console.error("live manifest missing", liveRes.status);
process.exit(2);
}
const live = await liveRes.json();
const same = live.tree === manifest.tree;
console.log(JSON.stringify({
same,
local_tree: manifest.tree,
live_tree: live.tree ?? null,
cache_control: liveRes.headers.get("cache-control"),
age: liveRes.headers.get("age"),
}, null, 2));
process.exit(same ? 0 : 1);
Insertion order keeps the tree stable, because the loop follows the files array rather than sorting by hash. Two builds of the same bytes should share a tree even when built_at changes. If you fold the timestamp into the tree, every republish looks like drift, and the note becomes noise.
The deploy step has to publish build-manifest.json as raw bytes. If a templating pass rewrites files on the way out, leave the manifest alone. A mismatch is not automatically a host defect. It is a fork: the upload skipped a file, the process is still serving an older directory, or a cache answered in place of the origin.
A second probe catches the split where the manifest matches and the HTML does not. Set CHECK_ORIGIN to the fixture host, then compare the page you just built with the page the network returned.
curl -fsS -D - -o /tmp/live-index.html \
-H "Cache-Control: no-cache" \
"$CHECK_ORIGIN/index.html"
shasum -a 256 /tmp/live-index.html index.html
If those digests differ while the manifest trees match, the notes should say the manifest and the page are not on the same path. That is a routing or cache split. Calling it a green deploy would be a category error. Some Node versions also ignore the Fetch cache field, which is why the curl headers stay in the workflow as a second witness.
The first hours are bookkeeping. Build once, freeze the file list inside the script, and record the local tree hash. Do not put secrets, environment files, or anything private into that manifest. A hash of public assets is the whole payload.
Each later edit gets the same treatment. Rebuild, republish the fixture, run the script, and keep three fields: local tree, live tree, and the cache-control value. The assistant's claim that an upload was accepted sits beside the exit code. It does not replace it.
A free host is a reasonable dry-run target when the current terms allow a public fixture and you can delete it afterward. It is a poor place for a customer database, an access token, or a private repository. Halfway through the window, run the check again without rebuilding.
Ephemeral disks and idle free-tier processes sometimes return a default page, or nothing at all. If the live manifest vanishes, the earlier 200 was a snapshot, not a lease. Write that down as a hosting constraint. Do not file it as a model failure just because a chat was open at the time.
By the end of the second day, the repeatable part is obvious. Run the comparison yourself, on a public fixture, with a cache-bypass request, and trust the exit code. The part that is not worth repeating is asking a model to confirm production from a transcript. A transcript cannot see a stale edge cache.
If you want help reading a mismatch, paste the script output into the assistant and leave the decision in the process status. That split is the whole lesson of the pass. The model may narrate. The files have to agree.
Four shapes cover the notes, and they should stay descriptive. Same tree and same HTML hash means the fixture and the workspace agree at that moment, nothing more. Same manifest and different HTML means a split route or a rewrite.
A different manifest with age at zero usually means the upload landed somewhere else, or the process never reloaded. A different manifest plus a long max-age should be treated as cache until a bypass request says otherwise. None of these shapes measure speed, token spend, or model quality. They only separate a finished chat from an origin that is serving this tree.
The check breaks in predictable ways. It cannot see server-side rendering that ignores static files. It cannot prove you deployed to the host you intended if CHECK_ORIGIN points at the wrong name. A proxy can cache build-manifest.json as eagerly as it caches HTML.
cache: "no-store" is a request preference, not a contract. Read the curl response headers before you blame the build step. Clock fields inside the manifest are labels, not evidence. A health URL that returns 200 without serving the manifest is the same class of miss as yesterday's page: the route answered, and the artifact did not.
Skip this approach if the fixture would contain credentials, personal data, or private source. Skip it if you need an SLA, a retention guarantee, or a compliance boundary. A dry-run host, free or not, is not that environment.
Skip it if the bug you care about is application logic. Artifact drift is the only question this script can answer. People who would treat a chat transcript as a production gate should not add another host to that habit. The extra machine would only give the transcript a new place to be wrong.
Quotas and permanence stay outside the note. A banner that advertises a large free token allotment is not repeated here, because that figure was not checked against a primary source on the day of writing. Read the current docs, then decide. If a dry run on the free server option is still listed when you do, point the script at a public fixture and keep the exit code as the line you trust.
Top comments (0)