/goal

Give Claude Code a durable objective and it won't stop until the work is done.
Budgets, deadlines, progress notes, idle detection, and a Stop-hook guard that
keeps sessions alive across hours or days -- all in a single file with no
dependencies.
A project slash command plus Stop hook for Claude Code that turns one durable
objective into a long-running, inspectable, resumable session. Built for runs
that may last hours or days without losing track.
/goal [<subcommand>] [--tokens N] [--deadline D] [--json] <objective>
No runtime dependencies. The helper stays compatible with Node 12.22+ because
some Bash environments resolve /usr/bin/node to an older system Node. The
test suite uses node:test, so development and CI should use Node 18+.
Install
Claude Code plugin (recommended)
From Claude Code, run:
/plugin marketplace add bullish0x/goal-cc
/plugin install goal@goal-cc
This installs the packaged plugin from plugins/goal/, including the /goal
command, helper script, and Stop hook. Restart Claude Code so everything loads.
For a project-shared install, use Claude Code's project scope:
/plugin marketplace add bullish0x/goal-cc
/plugin install goal@goal-cc --scope project
If marketplace add prompts for a scope inside Claude Code, choose project.
As of 2026-05-09, the official non-interactive CLI also supports declaring the
marketplace at project scope:
claude plugin marketplace add https://github.com/bullish0x/goal-cc.git --scope project
claude plugin install goal@goal-cc --scope project
If you are testing a local checkout before publishing, run:
claude --plugin-dir ./plugins/goal
Manual
Place this repository at the root of the project where you use Claude Code.
Claude Code reads project commands from .claude/commands/ and the project
Stop hook from .claude/settings.json. Restart Claude Code so both load. The
Stop hook can be disabled by deleting the Stop entry from
.claude/settings.json.
If you already have a .claude/settings.json with other hooks, merge the
"Stop" entry into your existing file instead of replacing it.
Direct project lifecycle helper
For direct project installs from a local checkout, use the lifecycle helper
instead of manually copying files:
npm run goal:install -- --project /path/to/project
npm run goal:update -- --project /path/to/project
npm run goal:uninstall -- --project /path/to/project
The helper manages only the direct project files:
.claude/commands/goal.md
.claude/scripts/goal-helper.mjs
- the managed
/goal Stop hook inside .claude/settings.json
It never reads, writes, deletes, or rewrites .claude/settings.local.json.
When editing .claude/settings.json, it preserves unrelated keys and unrelated
hooks. uninstall removes the managed Stop hook but leaves other settings in
place, and it skips command/helper files that were customized unless you pass
--force.
Heads-up for Windows users
The .claude/ directory is the only thing that wires up the command, Stop
hook, and helper script. Some Windows copy flows and globs skip hidden or
dot-prefixed directories, so verify that .claude\ came along or npm test
will fail with missing-file errors.
Before you copy, turn on hidden items so you can see .claude\ and
.gitignore:
- File Explorer: View tab -> check Hidden items.
- PowerShell:
Get-ChildItem -Force lists hidden and dot-prefixed entries
reliably.
Then copy with a tool that preserves dotfiles. PowerShell handles them
correctly when you use * plus an explicit .claude pass:
$src = "C:\path\to\source"
$dst = "C:\path\to\destination"
Copy-Item -LiteralPath "$src\*" -Destination $dst -Recurse -Force
Copy-Item -LiteralPath "$src\.claude" -Destination $dst -Recurse -Force
Robocopy can mirror everything including hidden entries in one shot. Only use
/MIR when the destination is disposable or already meant to exactly match
the source, because it deletes destination files that are not present in the
source:
robocopy C:\path\to\source C:\path\to\destination /MIR
After copying, verify the hidden directory landed:
Test-Path .\.claude\commands\goal.md
Test-Path .\.claude\settings.json
Test-Path .\.claude\scripts\goal-helper.mjs
All three must print True. If any prints False, re-copy with hidden items
visible. Missing .claude causes npm test to fail because the command,
Stop hook, and helper script live there.
Linux / Mac manual install
Clone or copy the repository into your project, then verify:
test -f .claude/commands/goal.md && echo OK || echo MISSING
test -f .claude/settings.json && echo OK || echo MISSING
test -f .claude/scripts/goal-helper.mjs && echo OK || echo MISSING
All three must print OK. If you cloned from GitHub, the .claude directory
is present by default (no hidden-file issues on Linux/Mac). Restart Claude Code
so the command and Stop hook load.
Quick tour
Start a goal with a soft token budget and deadline, add a progress note,
check status, and complete when done:
/goal --tokens 100K --deadline 2h Refactor the auth module to use the new token format
/goal note Split out token validation into its own helper; tests passing so far
/goal status
/goal complete
The Stop hook will keep the session alive while the goal is active. If the
agent hits a hard blocker or refusal, the goal auto-pauses instead of looping.
Paused goals resume with /goal resume. See status and history as JSON with
--json.
What it gives you
- One active goal per session, persisted in
goal-state/goals.json (atomic
write + temp/rename, file-locked across processes).
- Shell-safe objective capture: slash-command arguments are passed through a
quoted heredoc, so multiline objectives, nested quotes, apostrophes, and
parentheses are preserved literally instead of being re-parsed by Bash.
- Prompt-structure hardening: model-visible continuation prompts wrap escaped
objective text in
<untrusted_objective> so objective text cannot break out
into fake higher-priority tags.
- Stop-hook continuation that blocks Claude Code from stopping while a goal is
active, with a hard cap on auto-continuations to prevent runaway loops.
- Soft
--tokens budget and soft --deadline (durations like 30s, 45m,
2h, 1h30m, 1d); only active time counts toward the deadline. When
Claude Code provides a Stop-hook transcript_path, the helper reads bounded
JSONL usage snapshots and marks the goal budget_limited once observed usage
reaches the token budget. When the deadline elapses, the Stop hook marks the
goal deadline_limited and stops automatic continuation until the user
extends the relevant limit.
- Progress notes (
/goal note), capped at 200 per goal, that surface in both
status and the Stop-hook continuation prompt so progress carries across
resumes.
- Heartbeat tracking: every mutation stamps
lastActivity. Status, JSON, and
Stop-hook continuation surface idle time and warn past a configurable
threshold so day-long sessions cannot silently stall.
- Outcome-aware archive:
complete, cleared, and aborted <reason> are all
preserved in a 50-entry history (/goal history [N] [--json]).
- Refusal / hard-blocker auto-pause: if the agent's last message looks like a
refusal or concrete "blocked on ..." message, the Stop hook pauses the goal
instead of looping. Incidental uses of the word "blocked" do not auto-pause.
- JSON output for scripting:
/goal status --json, /goal history --json.
- Read-only diagnostics:
/goal doctor reports command/helper presence, Stop
hook count, state health, lock status, local settings presence, and session
candidates without mutating goal state.
For direct project installs, the command file is .claude/commands/goal.md,
the Stop hook is configured in .claude/settings.json, and all logic lives in
.claude/scripts/goal-helper.mjs. For Claude Code plugin installs, the
marketplace lives at .claude-plugin/marketplace.json and the plugin package
lives at plugins/goal/ with commands/, hooks/, and scripts/ at the
plugin root.
Command reference
/goal <objective> Start a goal.
/goal --tokens 250K <objective> Start with a soft token budget.
/goal --deadline 2h <objective> Start with a soft time deadline.
/goal Show status.
/goal status Same as above.
/goal status --json JSON status (or null if no goal).
/goal pause Pause; Stop hook will not continue.
/goal resume Resume a paused goal.
/goal complete Archive the goal as outcome=complete.
/goal clear Archive the goal as outcome=cleared.
/goal abort <reason> Archive as outcome=aborted with reason.
/goal extend --tokens N Adjust token budget mid-flight.
/goal extend --deadline D Adjust deadline mid-flight.
/goal extend --tokens N --deadline D Adjust both at once.
/goal note <text> Append a progress note.
/goal touch Refresh the heartbeat (defeats idle warn).
/goal doctor Diagnose install, settings, state, lock, and session.
/goal history [N] Show last N archived goals.
/goal history --json Same, JSON.
<objective> is free text (max 4000 characters). Quoting is only needed if
the text contains characters your shell would interpret, but in slash-command
form the entire $ARGUMENTS string is passed through unchanged. Apostrophes,
backslashes, embedded quotes, newlines, parentheses, and embedded
---flag-looking words are all handled.
How long-running sessions stay healthy
Designed for sessions that run for hours or days:
- Heartbeat + idle warning. Every mutation refreshes
lastActivity. Past
the warn threshold (CLAUDE_GOAL_IDLE_WARN_SEC, default 1800s), status
shows Idle: 2h 15m (idle warning: > 30m) and the Stop-hook continuation
reason includes a triage push. Use /goal touch to acknowledge "still
working on this" without making any other change.
- Soft deadline. Only active time counts; pausing pauses the clock.
Overdue active goals add a triage push to status. If a Stop hook fires after
the deadline has elapsed, the helper marks the goal
deadline_limited and
allows Stop. Run /goal extend --deadline D to reactivate it with more time.
- Transcript-backed token budget. If Claude Code includes
transcript_path
in Stop-hook input, the helper reads the transcript JSONL file, sums exposed
Anthropic-style usage fields (input_tokens, cache creation/read input
tokens, and output_tokens), and stores the largest observed total so
repeated Stop hooks do not double count. At or above the budget it marks the
goal budget_limited and allows Stop. Run /goal extend --tokens N with a
value above observed usage to reactivate. If no transcript usage is available,
the token budget remains a displayed soft budget rather than fake accounting.
When Claude Code is routed through CCR or another Anthropic-compatible