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.md at 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.md to
$(git rev-parse --git-common-dir)/info/exclude when the line is absent. Never edit the tracked
.gitignore for 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. A Read from line 1 with limit: N returns 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.md to .claude/binnacle.md in the worktree root. The
template ends at the ## Entradas marker 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 review executed manually by the user in a clean session — never by the run.
- Set
read_until_line to 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/plans and list ls -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 failed ls means the file does not exist — route to step 4, never
to none. 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_path
target. Suffix -2, -3, ... until the name is free, write the full approved plan there, and
verify with ls.
Resuming
When a session starts cold in a worktree that carries a binnacle:
- Status gate. Read the frontmatter first (
Read with limit: 12) and branch on status:
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: done and last_updated. Entries untouched. Stop. |
| Reports review findings to fix |
Set status: in-progress, add a new working session to Plan de sesiones with its blocks, bump the total and current_session, register that session's tasks (final task invoke 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.
Read the file with offset: 1, limit: <read_until_line>. limit is 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, never base_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-binnacle to write a
recovery entry first (it defines the shape), listing only what git show proves and never
inventing decisions no commit evidences. Then re-run steps 0 and 1: log-binnacle rewrote
current_session, status, read_until_line, and last_updated, so every value read before
the recovery entry is stale. If the refreshed status is no longer in-progress, follow that
branch of the gate and stop — a recovery entry can close the run's last working session.
- Read the plan.
Read the file at plan_path in full (skip only when plan_path is
none). 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_path is frozen against rewriting, not against repair: once the
user confirms the new path, verify it with ls, update plan_path and last_updated, and
note the repair in the next session entry's Desviaciones.
- Re-register the current session's tasks with
TaskCreate from the Bloques section — one
task per subtask, block code as the first token of each title (A1: ...), and the final task
always invoke 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.
1---2name: binnacle3description: Open, structure, and resume the durable log of a multi-session run: a .claude/binnacle.md file at the root of the run's worktree, created from a bundled template, ignored through the repo's local exclude file, and written as an immutable reverse-chronological changelog with a line-count cursor (read_until_line) so a cold session restores the run with one bounded Read. Invoke when task-planning opens a run, and whenever a fresh session finds a binnacle in its worktree and must resume the run it describes. Session-closing entries are written by the log-binnacle skill, not here.4---56# Binnacle78A substantial task does not fit one context window. The work is split into **sessions** separated9by `/clear`, and everything the conversation-scoped task list knows dies at each cut. The binnacle10is the durable layer between sessions: one markdown file per worktree that carries the session11plan, the run's state, and an immutable changelog of what every closed session did and decided.12In pattern terms it is the run's memento — the externalized snapshot a later session restores13without having lived the original context.1415Execution needs no skill of its own: each session works in the main context with its normal16tools, guided by this file. The binnacle's *Convenciones de ejecución* section carries the few17rules that must survive the cut (commit per block, immediate `TaskUpdate`, `log-binnacle` as the18last task of every session).1920## Where it lives, and why it is never committed2122- Path: `.claude/binnacle.md` at the root of the run's worktree. One worktree, one run, one file.23- Every run lives in a dedicated worktree — task-planning enforces this. A binnacle in the main24 working tree would be inherited by whatever branch checks out there next.25- Ignore it through the repo's local exclude file: append `.claude/binnacle.md` to26 `$(git rev-parse --git-common-dir)/info/exclude` when the line is absent. Never edit the tracked27 `.gitignore` for this — the exclude file is unversioned, applies to every worktree of the repo,28 and the line is written once for all future runs.2930## Structure: an immutable changelog with a cursor3132The file (see `references/binnacle-template.md`) has four zones, top to bottom: frontmatter,33*Convenciones de ejecución*, *Plan de sesiones* with its *Bloques*, and *Entradas* — the34changelog, most recent entry first, each new entry inserted directly below the `## Entradas`35marker. Frontmatter keys stay in English for parse stability; the body is written in Spanish.3637Two frontmatter keys carry the mechanics:3839- `read_until_line: N` — the cursor. A `Read` from line 1 with `limit: N` returns exactly the hot40 prefix: frontmatter, conventions, session plan, and the most recent entry. Because new entries41 insert at the top of the entries zone, the hot prefix stays a stable size no matter how long42 the run gets; history below the cursor is never loaded unless an entry points to it.43- `plan_path` — the approved plan this run implements. Future sessions re-read the original plan44 from disk instead of reconstructing it from a conversation that no longer exists.4546Mutability is per zone, stated once:4748| Zone | Mutability | Who writes it |49|---|---|---|50| Frontmatter: `current_session`, `status`, `last_updated`, `read_until_line` | mutable | `log-binnacle`; `status: done` by the review session on the user's confirmation |51| Frontmatter: `task`, `branch`, `worktree`, `base_branch`, `base_sha` | frozen after opening | nobody |52| 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 |53| Convenciones de ejecución | frozen | nobody |54| Plan de sesiones / Bloques | mutable: checkboxes, plus re-planning of unchecked sessions and unfinished blocks | `log-binnacle` |55| Entradas | append-only at the top; existing entries immutable — never edited, deprecated, or summarized | `log-binnacle` |5657Where this skill and the binnacle's own blockquote ever disagree, this skill wins.5859## Opening6061task-planning opens the binnacle when it registers a run:62631. Copy `references/binnacle-template.md` to `.claude/binnacle.md` in the worktree root. The64 template ends at the `## Entradas` marker and its comment line — a freshly opened binnacle has65 zero entries.662. Ensure the exclude line exists (command above).673. Fill the frontmatter: `task`, `branch`, `worktree`, `base_branch`, `base_sha` (the frozen68 merge-base SHA — the only value ever used as a git revision), `plan_path` (resolved with the69 recipe below), `status: in-progress`, `current_session: 1 of <total>` where total counts70 working sessions only (Sesión R never counts), `last_updated`.714. Write *Plan de sesiones* and *Bloques* from the decomposition. The plan always ends with a72 `Sesión R — code review` executed manually by the user in a clean session — never by the run.735. Set `read_until_line` to the last line of the file, verified with a command74 (`awk 'END{print NR}' .claude/binnacle.md`), not by eye.7576There is no `A0` task recording the base ref: `base_sha` lives in the frontmatter.7778### Resolving `plan_path`7980Every future session re-reads the plan from this path, so it must be an **expanded absolute81path** (`/home/<user>/...`, never `~`-prefixed — the `Read` tool requires an absolute path) to a82file that exists right now. Never fill it from memory, and never substitute a summary of the plan83inside the binnacle — the binnacle carries state, not specification.8485Which value to record:8687| Situation | `plan_path` |88|---|---|89| 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 |90| 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 |91| A direct implementation request with no plan artifact and no approved plan text — the decomposition being produced now is the only design | `none` |9293Recipe:94951. If the user named a path, expand it to absolute and go to step 3.962. Otherwise run `mkdir -p ~/.claude/plans` and list `ls -t ~/.claude/plans/`, keeping only files97 whose name or `# ` title matches this task. Exactly one match — use it. Zero matches — treat98 the plan as unsaved (step 4). Two or more matches — ask the user which one; never pick by99 recency alone.1003. Verify with `ls <path>`. A failed `ls` means the file does not exist — route to step 4, never101 to `none`. Record the expanded absolute path.1024. To materialize an unsaved plan, derive the filename from the run's branch, stripping the type103 prefix: `feat/http-client-extraction` → `~/.claude/plans/http-client-extraction.md`.1045. If that file already exists, do not overwrite it — it may be another run's frozen `plan_path`105 target. Suffix `-2`, `-3`, ... until the name is free, write the full approved plan there, and106 verify with `ls`.107108## Resuming109110When a session starts cold in a worktree that carries a binnacle:1111120. **Status gate.** Read the frontmatter first (`Read` with `limit: 12`) and branch on `status`:113 - `in-progress` — continue with the steps below.114 - `ready-for-review` — register no tasks and write no code until the user decides. Report115 the run's state and that the review is theirs to run. Then:116117 | User's response in this session | Do |118 |---|---|119 | Confirms the run is finished | Set `status: done` and `last_updated`. Entries untouched. Stop. |120 | Reports review findings to fix | Set `status: in-progress`, add a new working session to *Plan de sesiones* with its blocks, bump the total and `current_session`, register that session's tasks (final task `invoke log-binnacle`), then execute normally. |121 | Says nothing, or asks something else | Stay read-only. Do not touch the frontmatter. |122 - `done` — report that the run is closed and ask before doing anything in this worktree.1231. `Read` the file with `offset: 1, limit: <read_until_line>`. `limit` is a line count, so it124 equals the cursor only when reading from line 1 — never start the bounded read at an offset.125 The small overlap with the frontmatter peek is intentional.1262. Reconcile against `git log <base_sha>..HEAD --oneline` (`base_sha`, never `base_branch` — the127 branch moves, the SHA is frozen). Commits are the ground truth; where the binnacle and git128 disagree, trust git.1293. **Recovery.** If git shows commits that no entry above the cursor references, the previous130 session died before closing. Do not continue silently: invoke `log-binnacle` to write a131 recovery entry first (it defines the shape), listing only what `git show` proves and never132 inventing decisions no commit evidences. Then re-run steps 0 and 1: `log-binnacle` rewrote133 `current_session`, `status`, `read_until_line`, and `last_updated`, so every value read before134 the recovery entry is stale. If the refreshed `status` is no longer `in-progress`, follow that135 branch of the gate and stop — a recovery entry can close the run's last working session.1364. **Read the plan.** `Read` the file at `plan_path` in full (skip only when `plan_path` is137 `none`). The binnacle carries state — what was done and what comes next — not specification:138 the session about to execute needs the plan's intent and design decisions, not just its task139 titles. Each session runs only a slice of the run, so the full plan fits its budget.140 If the Read fails because the file no longer exists, stop before registering any task and ask141 the user for the plan's current location — do not glob `~/.claude/plans/` for a lookalike and142 do not proceed planless. `plan_path` is frozen against rewriting, not against repair: once the143 user confirms the new path, verify it with `ls`, update `plan_path` and `last_updated`, and144 note the repair in the next session entry's *Desviaciones*.1455. Re-register the current session's tasks with `TaskCreate` from the **Bloques** section — one146 task per subtask, block code as the first token of each title (`A1: ...`), and the final task147 always `invoke log-binnacle`.1486. Continue from the **Siguiente** section of the most recent entry. Read entries below the149 cursor only when the last entry points to them. When the binnacle has zero entries — the150 run's first session died before closing anything — there is no *Siguiente*: start from the151 first subtask of the first block of the current session as written in **Bloques**.152153## Closing a session154155The last task of every session is invoking the `log-binnacle` skill — it writes the session's156entry, advances the checkbox and `current_session`, updates `status` and `last_updated`, and157recalculates `read_until_line`. When the final working session closes, `status` becomes158`ready-for-review`: the user runs the code review manually (`/code-review`) in a clean session,159and the run reaches `done` only through the status gate above.