Ian Provencher
Podcast
← All field notes
Written here 4 min read

The completion meter

By Ian Provencher

This is the proof half of how I work with Claude Code. The planning half, Plan mode, enforced, ends with an approved plan and a fresh window that builds it while I am away. This post is about how that window, and I, know when the build is actually finished.

The problem is the word “done”

A long agent session ends with a summary, and the summary says the work is complete. Sometimes it is. When it is not, the gap shows up later, as a check that never ran or a file that exists without the thing it was meant to prove.

Anthropic’s Claude Code best practices put it plainly:

Claude stops when the work looks done. If it has nothing to run that tells it whether the work actually is done, “looks done” is all it has to go on.

The same page sorts every instruction into two kinds. A non-deterministic one is read by the model, which decides what to do with it. A deterministic one is carried out by Claude Code itself, every time. For “when it’s done”, it files /goal and “a request for evidence” under the first kind, and “Tests, typecheck, a Stop hook, CI” under the second. /goal is the condition a walk-away build runs against: a separate model reads the conversation after every turn and decides whether the condition is met. That judge is a model, so it is not deterministic either.

I measured my own setup on 19 October. Of 533 claims of done that the old meter had judged, 195 were refused. And 0 of 5 walk-away windows had counted anything at all.

What the meter does instead

The completion meter is a Claude Code mod: code that Claude Code loads into its own engine, which can watch session events and draw on screen. It counts an approved plan, and its rules come down to one sentence. An item is done when the main session runs that item’s own proof command, exactly as the plan wrote it, and the run exits clean.

Everything else follows from making that sentence hard to get around. The plan has to carry the proofs. The list is frozen at approval. A run that is not exactly the proof does not count, and one still running in the background does not count yet. My words close only the items that are mine, and only when they name the item. The count prints a verdict line, and the walk-away window’s goal is that line.

That last part is the design. The judge is still a model, but it no longer has to decide what “done” means. Code has already counted, and the judge reads one line.

The story below walks the whole thing, using this build’s own record. The panel beside it is a replica of the meter’s band, the one line it draws above the prompt. The band is shown as the meter printed it at each tally while this post was being built, with the newest proof run at that moment.

Terminal screenshot of a finished Claude Code session. A Recap box begins: I confirmed that all four posts serve the new build on the live site, and walked their stories there. Below it: Goal achieved (2h · 1 turn · 463.5k tokens). The strip reads effort xhigh, cache 59m, then its 5-hour, weekly, fable and context gauges; then 5h resets in 2h 15m, wk & fable reset in 6d 17h, session 2h 22m; then meter, Claude 17 of 17, goal met, counted 1 min ago.
In action: a screenshot of the build window that wrote these four posts, taken at its finish on 31 October at 10:05. The meter line reads Claude 17 of 17, goal met, and Claude Code reports the goal achieved.
  1. 01

    Done, too early

    Measured

    An agent that says it is finished has made a claim. On 19 October I measured how often those claims held in my own setup.

    2026-10-19, 23 planned sessions re-read
    walk-away windows that counted anything 0 of 5
    claims of done refused 195 of 533
    kept ticks resting on one bare file name 76 of 263

    A walk-away window is a fresh session that builds an approved plan with nobody watching. On 19 October all 5 of them had counted nothing, because only an approval in the window itself told Claude the meter was counting. Of the ticks the meter had kept, 76 of 263 rested on a single bare name such as REPORT.md: a file existing, not a check passing.

    So the question changed. Not "did Claude say it is done" but "which command ran, exactly as written, and how did it exit".

  2. 02

    The plan carries its proofs

    Enforced by a hook

    Every item in an approved plan ends with the command that proves it. The plan cannot be approved without one.

    ## Steps
    1. Correct the setup's decision log so its transcript-retention record says …
       Proof: `grep -q "Superseded 2026-10-21" …/DECISIONS.md`
    ## Verification
    5. No private name, path or secret appears in the posts' or stories' sources.
       Proof: `node …/scan-tree.mjs …/src/content/blog && …`

    The plan for this very post has 11 steps and 5 checks, and each carries a Proof: line read the way Markdown reads a code span. The plan-exit hook that guards approval refuses a missing proof, a bare name, a <placeholder> and a proof cut short inside its own backticks.

    Two items name no command. S10 is mine: "your answer in the final question box, which names S10". S11 is a later event, the live build after my go. The meter files both outside Claude's part, so neither can be closed by Claude running something.

  3. 03

    A proof must be able to fail

    Instructed

    A proof that cannot fail proves nothing. Each one is chosen so the wrong world prints a different answer.

    step 2's proof, the kit's suite then the privacy scan
    clean tree 0 hit(s) · exit 0
    planted address and employer name 2 hit(s) · exit 1
    so its zero can be believed

    This is a rule the plan's author holds, not something the meter can check. Step 2 is a case in this build: its proof chains the kit's test suite to a privacy scan with &&, so either failing fails the line. Before trusting the scan's zero, the build planted a private address and an employer's name in a scratch repository and watched the same scan exit 1.

    The same step fixed a check that could not fail. The kit's suite counted a skipped arm as a pass, so a machine missing a tool printed the same total as one that ran everything. It now prints a skip count beside the passes.

  4. 04

    Frozen at approval

    A mod

    At approval the list is frozen by wording. Editing the plan later cannot move a proof onto a different item.

    // stages.ts
    export const itemKey = (list, full) =>
      `${list}:${full.replace(/\*\*/g, '').replace(/\s+/g, ' ').trim().toLowerCase()}`

    Each item's key is its list and its whole text, so an inserted step or a reworded one leaves every other proof where it was. Before 19 October an inserted step silently dropped the proofs after it.

    A walk-away window never saw the approval happen, so the plan is carried to it: the launcher's /goal line names the plan file, and the meter freezes the list from that. This window's band read "approved in the planning window" from its first turn.

  5. 05

    Only an exact run counts

    The rule

    A run counts only when it is the main session's own shell call, and its command is the proof's whole command as written.

    export function runOf(u, sites, home) {
      if (!isEvidenceTool(u.tool)) return []
      const said = normCmd(String(input.command ?? ''), home)
      ...
      return sites.filter(s => normCmd(s.cmd, home) === said)
    }

    Equality, not containment. The matcher before 25 October accepted the proof anywhere in a command, so words before it, a longer word holding it, text typed into a browser and a helper's run could all count. normCmd forgives only what cannot change what runs: runs of spaces, a quoted plain path, and the home folder however it is written.

    This build shows the rule biting. Step 3's first build ran with its output sent to a log file, a different command, and the meter ignored it. The second ran exactly as written and proved two items at once, S3 and V1, because the plan gives both the same proof.

    Measured before the change: of 287 proofs run as their whole command in 30 days, 285 were typed exactly as written, and the other 2 differed only in path spelling, which normCmd reads one way.

  6. 06

    The exit code decides

    The rule

    The run's own exit decides. Nobody reads the output and judges it.

    export function exitedClean(isError, result) {
      if (isError) return false
      const why = isRecord(result) ? result.returnCodeInterpretation : undefined
      return !(typeof why === 'string' && why.trim() !== '')
    }
    $ grep -q "Superseded 2026-10-21" …/DECISIONS.md
    (no output)
    exit 0 → S1 proved

    Step 1's proof prints nothing at all. grep -q answers only with its exit code, and that is the point: the meter reads the tool's error flag and Claude Code's own reading of the return code, never the text, which is the command's output and could say anything.

  7. 07

    A background run waits for its notice

    The rule

    A proof started in the background, or one that outran its time limit, proves nothing until the engine reports how it ended.

    Bash · run_in_background → a task id comes back
    meter pending · a start is not an end
    engine notice for that task · completed (exit code 0)
    meter proved

    Before 25 October one window kept a timed-out run as a pass, and that run's later failure never reopened it. Now an answer carrying a background task id marks the run pending. The task id is read from the answer's own field, never its text, and only the engine's own finish notice ends it. A line in a user message that looks like a notice ends nothing, because a pasted message or a peer session could carry one.

  8. 08

    A newer failure reopens

    The rule

    Each item keeps only its newest run. A pass proves it on the spot, and a later failure takes the proof back.

    first run · passes → proved
    the work it proves is edited
    next run · fails → open again
    fixed, run again · passes → proved

    The rule's shape, not a record. This build's real runs are on the panel and in the last stage.

    Before 25 October Claude claimed an item after a passing run, and the claim was what counted. The claim step is gone: the count sees the run and decides, so there is no moment where an item reads done because Claude said so. Building on a proved step and breaking it reopens it, which is exactly when it should reopen.

  9. 09

    My words close only what they name

    His call

    Some items only I can close. My words close them, and only words that name the item.

    S10 · proof: yours, your answer in the final question box
    box answer · "S10 … go"
    quote names S10 ✓ · from my box ✓ · after approval ✓

    A quote counts only from a prompt typed at my terminal, sent from my phone, or my answer in a question box, and only when that prompt or box names the item's id. My answer about S2 decides nothing about S3. The meter used to read every prompt event, which also fires for task notices and messages from other sessions, and 110 of 140 quotes it had kept as mine were those.

    My words never stand in for a command. On an item in Claude's part they count only once Claude has handed the item over as something nothing it can run will close.

  10. 10

    Seven stages on one band

    A mod

    The whole count lives on one line above the prompt, beside the HUD.

    ✓ Intent · ✓ Explore and design · ✓ Plan approval
    ● Build steps proved · ○ Verify checks proved
    ○ Commit and push · ○ Close
    Claude 0 of 17 ▱▱▱▱▱▱▱▱▱▱ · 1 yours · 1 later

    Seven stages, each done, under way, waiting or skipped. The headline counts Claude's part: its own items, plus commit, push and close, which is why this plan's 14 items make a part of 17. The bar fills whole cells rounded down, so it never reads full until every unit is done. Items planned as mine or as a later event's sit beside the count, never inside it.

    Commit and push is read from git alone. The meter asks git whether each edited file is committed and pushed, and an edit after a commit sends the stage back.

  11. 11

    The tally and its verdict

    A tool call

    Asked with no items, the meter prints the tally: what is open, who acts on it, and a last line that is the verdict.

    Tally of i-want-a-new-crystalline-stroustrup.md: Claude's part 0 of 17
    Open, Claude acts (14):
    ● S1 Correct the setup's decision log …
      closes: run `grep -q …` as the whole command, exactly as written
    ◆ S10 · the operator acts   ◷ S11 · a later event acts
    VERDICT: NOT DONE (15 open)

    This is this build's first tally, at 07:41 ET. Every answer the tool gives ends with that line, and the verdict asks exactly what Claude's part asks: every item proved, and commit and push done or not needed. Until 25 October the goal and the count judged different lists.

  12. 12

    The goal reads the tally

    Built into Claude Code

    The walk-away window's goal is the tally. A separate model reads it after every turn and decides whether the build may stop.

    /goal Carry out the sealed plan at …stroustrup.md; …
    … Done when the latest tally, printed with that tool after the last change, ends with the line
    VERDICT: ALL DONE.

    The condition this window started with ran to 2,500 characters. Its done line names one thing a judge can read in the conversation: the tally's last line. The judge runs no commands, so the evidence has to be printed.

    From the record of 30 October, a different build: its tally read ALL DONE, and the judge refused to stop six times running. One check had been handed to the next morning's run, the meter counted that as not blocking, and the judge read the plan and the tally and said the check was not proved. It was right. The meter's count and the judge's reading disagreed, and the build stopped only when the operator ended it.

  13. 13

    Measured

    Measured

    Two weeks of fixes, each with its numbers, and this build's own runs.

    • 15 October. The band and the panel are born: seven stages, and every plan must list its steps.
    • 19 October. Proof lines and the frozen list. Measured first: 0 of 5 walk-away windows counted anything, and 195 of 533 claims were refused.
    • 25 October. Twelve plan steps in a day: exact runs, background notices, no claim step, his words only his, one tally. The exact-run tests: 10 of 12 failed before the change and all passed after.
    • 26 October. The walk-away's goal becomes the tally, and a real walk-away is judged end to end on the four failures I named.
    • This build, to the cut. 11 proof runs: 11 passed and 0 failed.

    Dates and before-and-after counts are from the meter's commit messages; the build's runs are from its session log, judged by the meter's own code.

In action

Two terminal windows. In front, a new Claude Code window acknowledges its goal and prints the completion meter's tally: Claude's part 0 of 16, proved none yet. Its strip ends meter, Claude 0 of 16, and /goal active. Behind, the planning window's recap ends with its strip: meter, Handed off, the walk-away window counts the build.
In action: a screenshot of the first tally in the walk-away window for a search-helper upgrade, 29 October at 7:57 pm. The meter counts Claude 0 of 16 before anything is built, and the planning window behind it says Handed off: the walk-away window counts the build. The grey boxes cover my user name.

What it does not do

It does not judge whether a proof is a good one. “A proof must be able to fail” is a rule the plan’s author holds, and the meter cannot see it. A plan whose proof is true will be proved by true. The defence is upstream: the planning method asks for a check whose answer moves, and the build runs a control beside each one.

It does not read output. A test runner that exits 0 after printing a failure will be counted as a pass. The meter trusts the exit code because the alternative, reading a command’s text and deciding what it meant, is the claim it was built to replace.

And it is not the judge. The /goal judge is a separate model that reads the conversation after each turn. The meter’s job is to print one line that judge can read without running anything. On 30 October the two disagreed, six times in a row, about an item handed to the next morning. The judge was right, and the build stopped only when I ended it.

Where the code lives

The meter lives in my private setup repository, so this post quotes it rather than linking it. Its rules sit in one file of pure functions, tested by its own suite and by a replay that re-counts every recorded session and judges the count a second way. The planning half’s hooks are public in the intent-lock kit.

#claude-code#workflow#ai#build