The first fifteen minutes of an AI coding session usually fail before the model ever writes a line. Ceremony eats the clock while you hunt directories, env files, and a test that actually runs. I now refuse to send the first real prompt until a local probe returns green. That single habit has mattered more to me than swapping models around on any free tier.
Think of those minutes like the cold start of a serverless function you forgot to warm. The user is already in the room, tapping the table, while your stack still looks for a runtime. Agents fill that awkward silence with confident guesses, because sitting quietly feels like a product failure. Why would a coding agent wait politely when it can hallucinate a package manager and keep moving?
I started treating minute one through minute fifteen as a developer-experience surface, not as leftover warm-up you shrug away. The question is blunt: where did the clock actually go before the first verified diff landed? If you cannot answer that without waving your hands, you are debugging folklore instead of a session. A cheap stopwatch plus one local probe beats another prompt that restates your entire stack.
What those fifteen minutes usually buy you
Most of the waste is not inference at all, which is why swapping models rarely repairs the lobby. You are still proving the workspace exists, the runtime matches, and a smoke test can fail loudly. A typical broken session chases the wrong Node binary first, then a dotenv file that never loaded. Does that feel like a model problem, or like a lobby with no signage and a guessy concierge?
Here is the pattern I keep seeing when a free coding stack suddenly looks slow in the editor. The window opens on a parent folder, and the agent assumes npm because some tutorial said so. You correct it in chat, which trains the session to negotiate instead of observing the tree. By the time a patch appears, it often targets a directory the process never listed with ls.
I wanted a gate that was rude in a useful way, like a bouncer who checks the stamp. The agent does not get to write application files until a local script prints BOOT_OK with a timestamp. That script stays boring on purpose, because boring is measurable while a chatty agent is not. If the probe cannot run, those opening minutes should halt the session rather than decorate a broken tree.
The one fix was a boot probe with a clock
Call the metric time-to-first-verified-diff if you need a name that survives a standup meeting. Start the clock when the folder opens, not when the first streamed token arrives in the pane. Stop the clock when a patch applies and the same probe still prints BOOT_OK without excuses. Everything between those two marks is developer-experience debt you can finally point at without folklore.
The harness below is a proposed local timer, not a benchmark and not a vendor leaderboard in disguise. Drop dx-boot.mjs at the repo root and treat a red exit code as a closed door. If that feels heavy for a side project, ask whether an untimed agent is actually the lighter path. You can disagree with my steps, but you should not skip having steps you can rerun.
// dx-boot.mjs
// Proposed local harness — run before the first agent write.
import { execSync } from "node:child_process";
import { existsSync, writeFileSync, mkdirSync } from "node:fs";
import { resolve } from "node:path";
const started = Date.now();
const logDir = resolve(".dx-boot");
mkdirSync(logDir, { recursive: true });
function stamp(step, ok, extra = "") {
const ms = Date.now() - started;
const line = JSON.stringify({
t_ms: ms,
step,
ok,
extra,
cwd: process.cwd(),
});
writeFileSync(resolve(logDir, "timeline.ndjson"), line + "\n", { flag: "a" });
console.log(line);
return ok;
}
function must(cmd, step) {
try {
const extra = execSync(cmd, { stdio: ["ignore", "pipe", "pipe"] })
.toString()
.trim();
return stamp(step, true, extra.slice(0, 200));
} catch (err) {
stamp(step, false, String(err.message).slice(0, 200));
process.exitCode = 2;
return false;
}
}
const checks = [
() =>
stamp(
"cwd",
existsSync("package.json") || existsSync("pyproject.toml") || existsSync("go.mod"),
process.cwd()
),
() => must("git rev-parse --show-toplevel", "git-root"),
() => must("node -v", "node"),
];
if (existsSync("package.json")) {
checks.push(() =>
must("node -e \"require('./package.json'); console.log('pkg-ok')\"", "pkg-parse")
);
}
if (existsSync("package-lock.json") || existsSync("pnpm-lock.yaml") || existsSync("yarn.lock")) {
checks.push(() => stamp("lockfile", true, "present"));
} else {
checks.push(() => stamp("lockfile", false, "missing"));
}
const smoke =
process.env.DX_SMOKE ||
(existsSync("package.json") ? "node -e \"console.log('smoke')\"" : "true");
checks.push(() => must(smoke, "smoke"));
const ok = checks.every((fn) => fn());
stamp("boot", ok, ok ? "BOOT_OK" : "BOOT_BLOCKED");
if (!ok) {
console.error("Keep the session read-only. dx-boot did not print BOOT_OK.");
process.exit(2);
}
Run the file like a bouncer standing at the repo door, not like a dashboard you might glance at later. Export a real smoke command if you have one; the default only proves that Node can print a word. A green default is not a blessing, and I will not celebrate it as coverage. Then the shell one-liner either opens the door or keeps every application file read-only for a while.
export DX_SMOKE="npm test --silent -- --testPathPattern=smoke"
node dx-boot.mjs || echo "session stays read-only"
If you do not have a smoke test yet, do not let the agent invent one under src. Write the smallest failing check yourself, even if it only proves that GET /health is not a stack trace. The point is ownership of the clock, not a coverage number you could paste into a review. Would you let a stranger rearrange your kitchen before you confirmed the stove actually turns on?
I keep a second command for the moment a patch lands, because apply still belongs inside the window. You want the same probe after git apply, or after the editor writes the files to disk. If the probe flips red, the diff is not a gift; it is a rollback candidate with a timestamp attached. That restore line is the personality of the workflow, and it is intentionally unromantic about almost-right files.
git add -A
node dx-boot.mjs && git diff --stat
# if dx-boot exits 2:
git restore .
# then ask a narrower inspect-only question
The session does not get to pile up almost-right files while you negotiate tone in a side thread. You either keep a verified tree or you return to inspect-only questions like ls and git status. Have you noticed how often an agent prefers rewriting a file to reading a boring probe line? I would rather repeat node dx-boot.mjs than teach the chat a new apology format for missing lockfiles.
Where a free model and a free server actually belong
I only want extra compute for the probe and the smoke, not for another layer of onboarding ceremony. Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode's free model access and free server option matter here because the timer should live on a throwaway box. I will not name models or quote a quota, and if you try it, park the probe there then delete the box.
Use the remote box when your opening minutes are polluted by fans, Docker, and a browser with too many tabs. Clone the repo, run node dx-boot.mjs, and only then paste a task that includes the probe output. The agent should see BOOT_OK the way a cook sees a clean cutting board before the first knife comes out. If it cannot see that line, you are back in the lobby inventing floor plans with a smile.
A prompt I send after a green boot looks small, almost rude, and that is the intended texture. It does not restate the framework, the Node version, or the runner, because the probe already proved them. It asks for one change, names the paths, and forbids extra files the session might consider helpful. That is the DX win: fewer words from me after the clock starts, and fewer invented helpers from the agent.
BOOT_OK from node dx-boot.mjs is attached.
Do not create files outside the paths I name.
Change only src/health.js so GET /health returns {"ok": true}.
Re-run DX_SMOKE after the edit. If the probe fails, revert and explain.
Is that less magical than a three-page system prompt that recites your stack like a tourist brochure? Yes, and the first fifteen minutes get quieter because the brochure is replaced by a process exit code. Magic is what filled the silence with the wrong package manager and a confident little shrug from the pane. Quiet is what a stopwatch buys you when you are willing to look unfashionable in the chat.
What the log is for
The ndjson file is the teardown, and it is more honest than a feeling that the model seemed sluggish today. Open it and notice which step crossed a long gap without a story you could tell a teammate. Maybe git-root was instant and smoke stalled because dependencies were never installed on that throwaway box. Maybe cwd was wrong, and everything after that line was theater performed for a folder that was never the repo.
python3 - <<'PY'
import json
from pathlib import Path
prev = 0
path = Path(".dx-boot/timeline.ndjson")
for line in path.read_text().splitlines():
row = json.loads(line)
delta = row["t_ms"] - prev
prev = row["t_ms"]
flag = "OK" if row["ok"] else "FAIL"
extra = str(row.get("extra", ""))[:60]
print(f"{row['t_ms']:8}ms +{delta:8}ms {flag:4} {row['step']} {extra}")
PY
I care about deltas between steps, not about a vanity number I could screenshot for a recap thread. A session that boots cleanly and then spends the remaining window chatting is still a developer-experience miss. The clock does not pause for eloquence, architecture lectures, or a second restatement of the README. If the first verified diff is not inside that window, I treat the rest as design talk, not coding.
Who should not bother
This workflow is fussy on purpose, and some rooms should not hire a bouncer for the kitchen door. If you are pairing with a human who already knows the repo, the probe is delay dressed as virtue. If the work is a blank canvas with no tree yet, dx-boot.mjs has nothing honest to smell. Greenfield architecture talk does not belong behind a lockfile check, and I will not pretend otherwise.
Do not run the smoke command as root, and do not point DX_SMOKE at anything that mutates production data. The harness shells out, which means a clever string can become a footgun with a very short fuse. I also would not use this on a repo you are not allowed to execute, including unreviewed vendor dumps. A free server does not move that boundary; it only changes the machine where the footgun would live.
The probe will lie if your smoke test is a console.log that cannot fail on a bad import. That lie is on you, not on the model card, and it will waste the window with applause. I would rather keep a tiny assertion that dies than a suite that claps for every hallucinated helper file. Should you grow the check later? Yes, after those opening minutes stop feeling like a missing elevator in your own building.
I am not claiming that free AI coding is slow in some cosmic way that a blog post can settle. I am claiming the lobby is unmeasured, so every delay gets blamed on a model you barely configured. Time the cold start, block the first write, and keep the artifact small enough that you can delete it. The first fifteen minutes are a product surface; treat them like one, or keep arguing with a concierge that does not exist.
Top comments (0)