Binnacle
A substantial task does not fit one context window. The work is split into sessions separated
by /clear, and everything the conversation-scoped task list knows dies at each cut. The binnacle
is the durable layer between sessions: one markdown file per worktree that carries the session
plan, the run's state, and an immutable changelog of what every closed session did and decided.
In pattern terms it is the run's memento — the externalized snapshot a later session restores
without having lived the original context.
Execution needs no skill of its own: each session works in the main context with its normal
tools, guided by this file. The binnacle's Convenciones de ejecución section carries the few
rules that must survive the cut (commit per block, immediate TaskUpdate, log-binnacle as the
last task of every session).
Where it lives, and why it is never committed
- Path:
.claude/binnacle.mdat the root of the run's worktree. One worktree, one run, one file. - Every run lives in a dedicated worktree — task-planning enforces this. A binnacle in the main working tree would be inherited by whatever branch checks out there next.
- Ignore it through the repo's local exclude file: append
.claude/binnacle.mdto$(git rev-parse --git-common-dir)/info/excludewhen the line is absent. Never edit the tracked.gitignorefor this — the exclude file is unversioned, applies to every worktree of the repo, and the line is written once for all future runs.
Structure: an immutable changelog with a cursor
The file (see references/binnacle-template.md) has four zones, top to bottom: frontmatter,
Convenciones de ejecución, Plan de sesiones with its Bloques, and Entradas — the
changelog, most recent entry first, each new entry inserted directly below the ## Entradas
marker. Frontmatter keys stay in English for parse stability; the body is written in Spanish.
Two frontmatter keys carry the mechanics:
read_until_line: N— the cursor. AReadfrom line 1 withlimit: Nreturns exactly the hot prefix: frontmatter, conventions, session plan, and the most recent entry. Because new entries insert at the top of the entries zone, the hot prefix stays a stable size no matter how long the run gets; history below the cursor is never loaded unless an entry points to it.plan_path— the approved plan this run implements. Future sessions re-read the original plan from disk instead of reconstructing it from a conversation that no longer exists.
Mutability is per zone, stated once:
| Zone | Mutability | Who writes it |
|---|---|---|
| Frontmatter: current_session, status, last_updated, read_until_line | mutable | log-binnacle; status: done by the review session on the user's confirmation |
| Frontmatter: task, branch, worktree, base_branch, base_sha | frozen after opening | nobody |
| Frontmatter: plan_path | frozen after opening, except repair when the resume finds the file missing and the user confirms the new path (see Resuming, step 4) | that repair only |
| Convenciones de ejecución | frozen | nobody |
| Plan de sesiones / Bloques | mutable: checkboxes, plus re-planning of unchecked sessions and unfinished blocks | log-binnacle |
| Entradas | append-only at the top; existing entries immutable — never edited, deprecated, or summarized | log-binnacle |
Where this skill and the binnacle's own blockquote ever disagree, this skill wins.
Opening
task-planning opens the binnacle when it registers a run:
- Copy
references/binnacle-template.mdto.claude/binnacle.mdin the worktree root. The template ends at the## Entradasmarker and its comment line — a freshly opened binnacle has zero entries. - Ensure the exclude line exists (command above).
- Fill the frontmatter:
task,branch,worktree,base_branch,base_sha(the frozen merge-base SHA — the only value ever used as a git revision),plan_path(resolved with the recipe below),status: in-progress,current_session: 1 of <total>where total counts working sessions only (Sesión R never counts),last_updated. - Write Plan de sesiones and Bloques from the decomposition. The plan always ends with a
Sesión R — code reviewexecuted manually by the user in a clean session — never by the run. - Set
read_until_lineto the last line of the file, verified with a command (awk 'END{print NR}' .claude/binnacle.md), not by eye.
There is no A0 task recording the base ref: base_sha lives in the frontmatter.
Resolving plan_path
Every future session re-reads the plan from this path, so it must be an expanded absolute
path (/home/<user>/..., never ~-prefixed — the Read tool requires an absolute path) to a
file that exists right now. Never fill it from memory, and never substitute a summary of the plan
inside the binnacle — the binnacle carries state, not specification.
Which value to record:
| Situation | plan_path |
|---|---|
| The user named a plan file, or one exists on disk for this work — anywhere: ~/.claude/plans/, an in-repo spec/, a doc the user pointed at | that file's absolute path, verified |
| A plan was approved in this conversation (via ExitPlanMode, or written plan text the user accepted) but no file exists on disk | write it to disk first (steps 4-5), then that path |
| A direct implementation request with no plan artifact and no approved plan text — the decomposition being produced now is the only design | none |
Recipe:
- If the user named a path, expand it to absolute and go to step 3.
- Otherwise run
mkdir -p ~/.claude/plansand listls -t ~/.claude/plans/, keeping only files whose name or#title matches this task. Exactly one match — use it. Zero matches — treat the plan as unsaved (step 4). Two or more matches — ask the user which one; never pick by recency alone. - Verify with
ls <path>. A failedlsmeans the file does not exist — route to step 4, never tonone. Record the expanded absolute path. - To materialize an unsaved plan, derive the filename from the run's branch, stripping the type
prefix:
feat/http-client-extraction→~/.claude/plans/http-client-extraction.md. - If that file already exists, do not overwrite it — it may be another run's frozen
plan_pathtarget. Suffix-2,-3, ... until the name is free, write the full approved plan there, and verify withls.
Resuming
When a session starts cold in a worktree that carries a binnacle:
- Status gate. Read the frontmatter first (
Readwithlimit: 12) and branch onstatus:-
in-progress— continue with the steps below. -
ready-for-review— register no tasks and write no code until the user decides. Report the run's state and that the review is theirs to run. Then:| User's response in this session | Do | |---|---| | Confirms the run is finished | Set
status: doneandlast_updated. Entries untouched. Stop. | | Reports review findings to fix | Setstatus: in-progress, add a new working session to Plan de sesiones with its blocks, bump the total andcurrent_session, register that session's tasks (final taskinvoke log-binnacle), then execute normally. | | Says nothing, or asks something else | Stay read-only. Do not touch the frontmatter. | -
done— report that the run is closed and ask before doing anything in this worktree.
-
Readthe file withoffset: 1, limit: <read_until_line>.limitis a line count, so it equals the cursor only when reading from line 1 — never start the bounded read at an offset. The small overlap with the frontmatter peek is intentional.- Reconcile against
git log <base_sha>..HEAD --oneline(base_sha, neverbase_branch— the branch moves, the SHA is frozen). Commits are the ground truth; where the binnacle and git disagree, trust git. - Recovery. If git shows commits that no entry above the cursor references, the previous
session died before closing. Do not continue silently: invoke
log-binnacleto write a recovery entry first (it defines the shape), listing only whatgit showproves and never inventing decisions no commit evidences. Then re-run steps 0 and 1:log-binnaclerewrotecurrent_session,status,read_until_line, andlast_updated, so every value read before the recovery entry is stale. If the refreshedstatusis no longerin-progress, follow that branch of the gate and stop — a recovery entry can close the run's last working session. - Read the plan.
Readthe file atplan_pathin full (skip only whenplan_pathisnone). The binnacle carries state — what was done and what comes next — not specification: the session about to execute needs the plan's intent and design decisions, not just its task titles. Each session runs only a slice of the run, so the full plan fits its budget. If the Read fails because the file no longer exists, stop before registering any task and ask the user for the plan's current location — do not glob~/.claude/plans/for a lookalike and do not proceed planless.plan_pathis frozen against rewriting, not against repair: once the user confirms the new path, verify it withls, updateplan_pathandlast_updated, and note the repair in the next session entry's Desviaciones. - Re-register the current session's tasks with
TaskCreatefrom the Bloques section — one task per subtask, block code as the first token of each title (A1: ...), and the final task alwaysinvoke log-binnacle. - Continue from the Siguiente section of the most recent entry. Read entries below the cursor only when the last entry points to them. When the binnacle has zero entries — the run's first session died before closing anything — there is no Siguiente: start from the first subtask of the first block of the current session as written in Bloques.
Closing a session
The last task of every session is invoking the log-binnacle skill — it writes the session's
entry, advances the checkbox and current_session, updates status and last_updated, and
recalculates read_until_line. When the final working session closes, status becomes
ready-for-review: the user runs the code review manually (/code-review) in a clean session,
and the run reaches done only through the status gate above.