DEV Community

Quinn Wang
Quinn Wang

Posted on

Name the File Before You Accept

I stop trusting an AI coding session the moment I cannot name the file it would change. A fluent answer can still be a rumor until a patch, a path, and a dry run exist on disk. If those three things are missing, I am watching a polished demo rather than reviewing actual work. Would you merge a teammate's change if you could not point to it in git status?

The first fifteen minutes are where that confusion hides, because everything looks busy and almost nothing is inspectable. You open the tool, you paste a task, and a confident block of code arrives before your coffee cools. The spinner feels like progress, yet the working tree may still be exactly the tree you started with. Have you ever accepted a suggestion and then failed to find it with a simple status command?

That gap is the friction I care about, not the color of the onboarding screen and not the speed of the first reply. You can have a glowing editor, a scrolling chat, and a still-clean branch because the suggestion never left the transcript. A transcript is a conversation, while a patch is a claim you can refuse, replay, or hand to someone else. Which of those two artifacts would you rather defend when a reviewer asks for the change tomorrow morning?

So the one fix that mattered was boring, and that is why it survived contact with a real repository. I write the model's proposed change to a patch file before I let anything touch the working tree. Then I run a dry apply, record the repository fingerprint, and only then decide whether the suggestion deserves a human edit. Does that sound slower than clicking accept, or does it sound like the first moment the session becomes reviewable?

Here is a small Node script I would keep beside the repo while testing that habit. I am labeling it as a proposed local check, not as a benchmark and not as a result from a measured lab. It shells out to git, writes a receipt, and exits non-zero when the patch cannot be applied cleanly. You can drop the file in scripts/session-receipt.mjs and run it with Node eighteen or any newer release.

#!/usr/bin/env node
// Proposed local check. It does not call a model and it does not modify tracked files.
// Add .session-receipts to .gitignore so a path receipt never becomes a commit.
import { execFileSync } from "node:child_process";
import { mkdirSync, writeFileSync, readFileSync, existsSync } from "node:fs";
import { resolve } from "node:path";

const patchPath = resolve(process.argv[2] || "proposal.diff");
const outDir = resolve(".session-receipts");

if (!existsSync(patchPath)) {
  console.error(`missing patch: ${patchPath}`);
  process.exit(2);
}

function git(args) {
  try {
    return execFileSync("git", args, { encoding: "utf8" }).trim();
  } catch (error) {
    const detail = error.stderr ? error.stderr.toString() : error.message;
    console.error(detail);
    process.exit(2);
  }
}

mkdirSync(outDir, { recursive: true });

const head = git(["rev-parse", "HEAD"]);
const branch = git(["branch", "--show-current"]);
const status = git(["status", "--porcelain=v1"]);
const patch = readFileSync(patchPath, "utf8");

let apply = "clean";
try {
  execFileSync("git", ["apply", "--check", "--", patchPath], { stdio: "pipe" });
} catch (error) {
  const detail = error.stderr ? error.stderr.toString() : error.message;
  apply = `failed: ${detail.trim()}`;
}

const receipt = {
  recorded_at: new Date().toISOString(),
  head,
  branch,
  porcelain: status,
  patch_path: patchPath,
  patch_bytes: Buffer.byteLength(patch),
  apply_check: apply,
};

const stamp = receipt.recorded_at.replace(/[:.]/g, "-");
const receiptPath = resolve(outDir, `${stamp}.json`);
writeFileSync(receiptPath, JSON.stringify(receipt, null, 2));
console.log(receiptPath);
console.log(apply);
process.exit(apply === "clean" ? 0 : 1);
Enter fullscreen mode Exit fullscreen mode

I would run it only after saving the suggestion as proposal.diff, because the script will not invent a diff. A typical first pass looks like the three commands below, and none of them require a network call. If the dry run fails, I still have the receipt, and the working tree remains untouched by the suggestion. Is that not a better failure than a half-applied edit you only notice after you come back from lunch?

test -s proposal.diff
node scripts/session-receipt.mjs proposal.diff
git apply --check -- proposal.diff && echo "hunks fit; behavior still unproven"
Enter fullscreen mode Exit fullscreen mode

The commands only prove that a saved diff fits this tree, and they say nothing about whether the behavior is desirable. If the assistant printed a unified diff, I paste that diff into proposal.diff and I do not let the editor apply it for me. If it printed a whole file instead of a diff, I stop and ask for a unified diff against the current file. Why would I accept a rewrite I cannot compare line by line with the file already on disk?

This is where a disposable server becomes useful, and I want the relationship stated before the tool name does any work. Disclosure: This article was prepared as part of MonkeyCode's product outreach. The brief I was given treats free model access and a free server option as available, without a verified quota or hardware spec. I would use MonkeyCode's free server as a scratch workspace, then bring home only a patch that git apply --check already accepted.

I am not treating those availability claims as a performance result, because a free seat does not prove the patch is correct. Read the current docs before you rely on any allowance, since token budgets and server lifetimes move and this article will not chase them. If the docs and the product disagree with a number you saw in a post, believe the docs you can open today. Would you rather cite a changelog you just read, or cite a paragraph that cannot age honestly?

The teardown of those first fifteen minutes usually fails in the same three places, even when the model sounds sharp. First the session cannot prove which commit it saw, so the suggestion argues with a tree you no longer have. Then the accept control sits closer than the diff, so your hand finishes a review your eyes have not started. Have you checked whether your last accepted edit left a path a colleague could open without asking you?

Finally the only surviving record often lives in a chat log that your teammate cannot clone, rerun, or dispute. The receipt script answers the identity failure and the missing-record failure, and it slows the careless click enough to matter. The head and branch fields tell you which snapshot the check ran against, while porcelain shows whether the tree was already dirty. A failed dry run is still a successful diagnosis, because you learned the suggestion does not match this particular tree.

I would not use this approach when the change involves a secret, a migration, or a release commit your policy says must be signed. A local receipt is not an approval system, and it will happily bless a valid patch that deletes the wrong table. Do not paste production credentials into a hosted session just because that server was free to start. If your team needs audit trails, locked runners, or a human gate before model output exists, this scratch workflow is the wrong shape.

There is another limit that shows up quickly, and it is smaller than policy and just as able to waste an afternoon. A clean apply check does not understand intent, tests, or the comment you meant to update three files away. Binary assets, renames, and already-dirty files can make a tidy chat diff fail for purely mechanical reasons. When that happens, can you shrink the task until the patch names one file you could read aloud?

If I were handing this ritual to my future self, I would keep it under five minutes and refuse to add dashboards. Save the diff, run the receipt, read the hunks, and only then decide whether the suggestion earns its own branch. Once the receipt exists, the chat can close, because the claim finally has a filename someone else can open. If you want a disposable desk for that check, read the current free-server docs and ignore any allowance this post refused to freeze.

Top comments (0)