/goal in Claude Code: keep a session running until the condition holds

/goal in Claude Code: keep a session running until the condition holds

Anthropic shipped the /goal slash command. A session-scoped wrapper over a prompt-based Stop hook: you write a condition, a small evaluator reads the transcript after each turn, and the session ends only when it says yes. When to reach for /goal, when for /loop, when for a Stop hook — and why you write the condition for Haiku, not for the main model.

Jakub Kontra
Jakub Kontra
Developer

Claude Code got a new slash command, /goal, which keeps a session in autonomous mode for as long as it takes an external evaluator to confirm, after every turn, that the stated condition holds.

/goal keeps a session running until the condition holds

Syntax:

/goal all tests in test/auth pass and the lint step is clean

Once you submit the command, the first turn starts with the condition as a directive. The main model works normally: it calls tools, edits files. When the turn finishes, a small fast evaluator (Haiku by default) steps in and decides whether the condition holds. If it doesn't, Claude continues straight into the next turn. If it does, the goal clears itself and the session returns to its normal mode.

The status line shows a ◎ /goal active indicator and the elapsed runtime. No extra window, just a status line.

How it runs turn by turn

The mechanic is restrained. The main model finishes a turn and writes into the conversation what it considers the result: test output, git status, exit codes, a diff. Then it's the evaluator's turn. It reads the transcript and returns a short reason for why the condition does or doesn't hold. The reason shows up in the status view and in the transcript.

The condition has a 4,000-character ceiling, which is plenty for a layered description of the end state. If you want a safety net, add a runtime limit:

/goal migration to v2 API complete or stop after 20 turns

Evaluator token usage is an order of magnitude smaller than the main model's. That's the price for not having to type "keep going" between turns.

You write the condition for the evaluator, not for Claude

The evaluator calls no tools. It doesn't read files, doesn't run tests, doesn't do ls. It sees only what the main model wrote into the conversation.

That implies an anti-pattern. This doesn't work:

/goal code is clean and refactor is done

The evaluator has no way to verify what "clean" means. It will guess from what Claude wrote, and guesses tend to be optimistic.

Working conditions look like this:

/goal npm test exits 0, git status shows no uncommitted changes,
and no file in src/auth exceeds 200 lines

Three parts a good condition combines: a stated check that does the verifying (npm test exits 0), a measurable end state (git status shows no uncommitted changes), and a constraint that must not change (no file in src/auth exceeds 200 lines).

The consequence: for the evaluator to decide, the main model has to actually run the check and print the output into the conversation. If Claude just writes "fixed" and doesn't run npm test, the evaluator will say "not yet verified" and the turn continues.

/goal vs /loop vs Stop hook

Three tools with a similar aim — keep the session running — but a different trigger.

/loop starts another turn on a time interval. It fits monitoring and polling tasks where you're waiting on external state (deploy, build, queue). The stop condition is either manual, or the model deciding on its own that it's done.

A Stop hook in settings.json is deterministic and permanent. It runs after every turn, decides via its own script or prompt, and lives across sessions. If you have a rule like "after every turn, run tsc --noEmit", that belongs in a Stop hook.

/goal is session-scoped, conditional, ad-hoc. It lives only for the current session, evaluates via a prompt, and disappears as soon as the condition holds or you clear it. Implementation-wise, /goal is a wrapper over a prompt-based Stop hook. It doesn't fight the existing abstraction, it just makes it a one-line command for the kind of task you'd never write a settings hook for.

It composes cleanly with Auto mode. Auto mode removes per-tool prompts inside a turn; /goal removes per-turn prompts between turns.

Status, clear, resume

/goal with no argument returns status: the active condition, runtime, number of evaluated turns, token usage, and the evaluator's last reason.

To cancel, use /goal clear, or the aliases stop, off, reset, none, cancel. Starting a new conversation via /clear removes the goal automatically.

Only one goal can run at a time. A new /goal overwrites the active one without asking – convenient while you're tuning the condition, less convenient if you don't notice.

--resume and --continue restore the active goal from the previous session, but they reset turn count, timer, and the token-spend baseline. A goal that already held, or one you cancelled, doesn't get restored. Once the condition has held or you've thrown it out, reviving it in another session makes no sense.

Headless mode for CI and scripts

/goal works non-interactive via claude -p:

claude -p "/goal CHANGELOG.md has an entry for every PR merged this week"

It's the simplest way to turn Claude Code into a one-shot agent run with a clear exit condition. Ctrl+C works as a kill switch.

/goal is not available if you have disableAllHooks enabled at any level, or allowManagedHooksOnly in managed settings, and the workspace trust dialog has to be accepted. The reason is technical: a wrapper over a Stop hook can't live where hooks aren't allowed.

When to reach for /goal

/goal fits tasks where you know the end state but not how many turns it'll take to get there:

  • migrating a module to a new API
/goal all imports updated, npm test exits 0, no references to oldClient remain
  • implementing a design document
/goal all endpoints from docs/spec.md respond with expected status, integration tests pass
  • splitting a large file into modules under a size budget
/goal src/billing.ts split so no file exceeds 300 lines, tests pass
  • clearing a labeled issue backlog
/goal every open issue with label "good-first" has either a PR or a justification comment

When not to: tasks without a measurable end state. If you can't write the condition such that Haiku can decide it from the transcript, reach for /loop or write a deterministic Stop hook.

The default is shifting. Until now, "one prompt = one turn" was the rule, and everything in between you handled by hand: either waiting at the terminal, or writing a settings hook you rarely got around to. /goal with Auto mode moves the default toward "one prompt = a conditional block of session" that ends itself the moment the condition holds. For tasks with a clear end state, that's the difference between babysitting and giving an assignment.