Skip to content
xsyetopz edited this page Oct 23, 2026 · 1 revision

Finish

This page tells how the finish slice of the core plugin claudetto works. A user reads it to set a goal with /goal and to learn when Claude gets a note or a deny at the end of work. A contributor reads it before an edit of slices/finish/. The slice holds the old goal slice, the stop hook, and the box hook. None of them makes a model call.

What It Does for You

A goal is one check command. The Stop hook runs the check each time Claude tries to stop. If the check exits 0, the stop goes on and the goal is cleared. If the check fails, the stop is blocked, and Claude gets the tail of the output. The stop is blocked at most GOAL_BLOCKS_MAX (5) times in a row. Then the stop goes on, a line tells you why, and the goal stays set. A passed check clears the count.

The state is in .claudetto/goal.json of the project root. The file holds the check command. The hook reads the file at each stop, so the goal survives a compaction, a /clear, and a resume of the session.

How to Use It

/goal answers with no model turn. A plugin skill cannot replace the built-in /goal, because the plugin name is its prefix. So a command.run hook answers in its place.

Text Effect
A command Sets the check, and starts a turn. It replaces a goal that is already set.
status or nothing Shows the check.
clear Clears the goal.

A pair of backticks around the command is cut.

Check

The hook runs the command with sh -c in the project root. A check ends after RUN_TIMEOUT_MS (300,000 ms) and counts as failed. The hook keeps the last OUTPUT_TAIL_CHARS (1,500) characters of the output, because the error is at the end.

Box Hook

The box hook runs on Edit and Write of a tasks.md. It compares the boxes of the file before and after the call. A box that changes from - [ ] to - [x] needs a passed check in the session record. A task line that names check commands in backticks, such as just lab, needs one of them to have passed. A line that names none needs any passed just check or bun test run. Without it the hook denies the call and names the task id. A change of the text of a task, with the box as it was, passes. A tick that passes adds a cost line to the context: the cost of the session and the change of each plan share since the first reading. The skill apply ends its report with these figures.

Loop Block

The Stop hook also blocks a stop when the turn checked a task box of a tasks.md and that file still has an open box. The turn is the rows of the session after the last user row that holds text. The block names the next open task and tells Claude to go on with it, or to ask you with AskUserQuestion when a blocker stops it. The stop is blocked at most GOAL_BLOCKS_MAX (5) times in a row, with its own count. Then the stop goes on, and a line tells you why. A stop with no tick in the turn is never blocked, and it clears the count. The block applies only in a session whose record shows that /claudetto:apply ran. The record shows it as a user row with the tag <command-name>/claudetto:apply</command-name>, or as a Skill call of claudetto:apply. A tick in another session never blocks. The block waits while an agent of $.agent.list() is pending, running, or waiting. A failing $.agent.list() adds no block. A failing /goal check blocks first.

Stop Hook Notes

The Stop hook never blocks for a note. It sets a note for the slice Notes in two cases:

  • The last reply lists work as not done, not fixed, paused, or for later, and names no block that stopped it, such as a sandbox deny. The note names the item.
  • The last reply says "ready" or "done", and a file that the session edited is not committed, or a task box that an Edit of the session ticked has no passed check. The note names the files and the boxes.

The rules match on the form of the reply. The tests in tests/claudetto/finish/reply.test.ts hold a known-bad reply for each incident row and known-good replies that must set no note.

Built-In Parts That Are Off

  • The setup snippet sets modelProposedGoals to disabled, so the model proposes no goal.
  • The prompt slice leaves out the goal_status attachment.

Hooks

Hook Filter What it does
classic.Stop None Runs the check and blocks the stop until it exits 0, up to the cap. Blocks a stop of a turn that checked a task box while a box is open. Sets a note for a reply that defers work or says done with no proof.
tool.call Edit, Write The box hook for a tasks.md.
command.run /goal Answers the command.

A failure of the command.run hook answers Goal: <message>, so the built-in /goal never runs.

Related Pages

Clone this wiki locally