Pick up exactly where you left off — every session.
One .claude/state.json holds your project's objective, deliverables, and open
questions — and Claude Code, GitHub
Copilot CLI, and OpenAI Codex CLI all open the session with it, auto-shown as a
WHERE YOU ARE block before your first prompt.
Coming back to a project — a week later or an hour later — means rebuilding context
before you can do anything useful: what was I doing, what's already done, what's still
open? claude-state-drift keeps that answer in one small .claude/state.json and puts
it in front of you and the agent before the first prompt — no reconstructing it from
scrollback, memory, or git log.
- Orientation on every session start — the "WHERE YOU ARE" block shown above, generated from your project's real state: objective, current focus, what's done, and what's still open.
- Your objective, kept in view — in interactive sessions the objective and current focus are re-surfaced every few prompts, so they don't scroll out of reach (cadence tunable per project).
- Staleness nudges — get flagged when
state.jsonlooks out of date relative to recent work, or when a commit looks like it finished a deliverable. - Session handoffs — end a session with
/claude-state-drift:handoffand the next session in that project opens with the handoff embedded in its orientation: the narrative context (next move, gotchas, dangling threads) thatstate.jsondeliberately excludes. When context use runs high, a one-line nudge suggests composing one — at most once per session. - You stay in control —
state.jsonis never silently rewritten; updates are drafted and shown as a diff before they land. A curated north-star, not an auto-captured log. - Zero workflow change — all of the above is automatic, driven by hooks. You never have to remember to invoke anything; the commands exist for when you want manual control.
/plugin marketplace add goldenwo/claude-state-drift
/plugin install claude-state-drift
Then drop a starter .claude/state.json into your project — copy one from
SCHEMA.md — and start a session. Uninstall any time with
/plugin uninstall claude-state-drift.
copilot plugin marketplace add goldenwo/claude-state-drift
copilot plugin install claude-state-drift@claude-state-drift
The plugin's sessionStart and postToolUse hooks read the same
.claude/state.json and emit it as Copilot's additionalContext — one hook at
session start, one after commits. See
copilot/README-copilot.md for full instructions and
what each hook does. One scope note: the context-pressure handoff nudge is not
supported on Copilot (its userPromptSubmitted event doesn't inject context) — a
parked handoff still loads at session start there via the orientation hook.
codex plugin marketplace add goldenwo/claude-state-drift
codex plugin add claude-state-drift@claude-state-drift
Codex CLI has a Claude-compatible lifecycle hook system, so the same
.claude/state.json drives all four hooks there too — orientation, commit-transition,
focus re-inject, and staleness. See
codex/README-codex.md for what each hook does and the lite
(AGENTS.md) tier.
Four hooks — all automatic — and six optional commands, all reading one file:
flowchart LR
S[(".claude/state.json")] -->|SessionStart| O["WHERE YOU ARE<br/>orientation block"]
O --> W["you + Claude work"]
W -->|"every N prompts"| F["focus-check<br/>re-injects the objective"]
F --> W
W -->|"commit lands"| C["state-track-commit<br/>spots deliverable transitions"]
W -->|"session ends"| Z["state-staleness<br/>flags stale state"]
C --> U["update-state command<br/>reviewed edit, never auto-writes"]
Z --> U
U --> S
- Auto-injected at session start — zero action. A
SessionStarthook prints the "WHERE YOU ARE" block fromstate.json. Install it, drop in astate.json, and it runs on every session from then on; you never invoke anything. - You stay in control.
state.jsonis never silently rewritten. When there's an update to make — a finished deliverable, a shifted focus — it's drafted and shown to you as a diff before it lands. It's a curated north-star you approve, not an auto-captured log of everything you did — the honest edge over tools that scrape a session into state behind your back. - Nudged to keep it fresh. A
PostToolUsehook spots commits whose subject looks like a finished deliverable and points you atupdate-state; aStophook flagsstate.jsonwhen it looks stale (and suggestsstate-cleanonce olddonedeliverables pile up — it only ever flags, never auto-edits). In interactive sessions, aUserPromptSubmithook also re-surfaces the objective every few prompts (cadence tunable in.claude/hooks-config.json). - Handoffs carry the narrative.
/claude-state-drift:handofffirst walks the normal reviewedstate.jsonupdate, then parks a narrative handoff (.claude/handoffs/latest.md) that the next session's orientation embeds — rendered as clearly-marked untrusted briefing data, and superseded automatically the momentstate.jsonis updated again or a newer handoff lands. When a session's context use crosses a threshold (75% when the exact figure is available locally), the prompt hook injects a one-line suggestion to compose one — at most once per session, never on the same prompt as the focus re-injection. When the exact figure isn't available, a transcript-size token estimate can stand in, but only if you arm it yourself: the hook can't learn your context-window size, and an absolute default tuned for 200K windows fires far too early on 1M-window sessions. Sethandoff_nudge_tokensonce in~/.claude/hooks-config.json(machine-wide, plugin-owned config —150000≈ 200K windows,750000≈ 1M) or per project in.claude/hooks-config.json; theSTATE_HANDOFF_NUDGE_TOKENSenv var still works and wins when set.handoff_nudge_disableturns the nudge off andhandoff_nudge_pcttunes the exact-% threshold, same two files, sameSTATE_HANDOFF_NUDGE_*env overrides (see SCHEMA.md). - Honest scope note. That periodic re-surfacing rides on your prompts, so it's
interactive-only: a headless run with no user turns —
claude -p, an autonomously-driven SDK loop, CI — gets the one-time session-start orientation and nothing recurring. Everything is computed from local files and local git; nothing leaves your machine.
For when you want to check or change state deliberately rather than waiting for
a hook. Plugin commands are always namespaced in the Claude Code CLI — type
/claude-state-drift: and tab-complete:
| Command | What it does |
|---|---|
/claude-state-drift:where-am-i |
Print the orientation block on demand — objective, focus, deliverable statuses, recent commits. |
/claude-state-drift:update-state |
Draft an update to state.json from recent work and show the diff. Never auto-writes — you approve every change. |
/claude-state-drift:re-anchor |
Audit the current session against the objective and report alignment: on-track, mild drift, or significant drift. |
/claude-state-drift:stats |
Show this project's own telemetry — sessions, per-injection token cost, activity, and nudge→update conversion — computed locally (needs CLAUDE_HOOK_LOG=1). |
/claude-state-drift:clean |
Keep state.json lean: dry-runs state-clean, shows which old done deliverables would be archived, confirms with you, then archives them (to an append-only state-archive.jsonl; reversible). |
/claude-state-drift:handoff |
End the session cleanly: runs the reviewed update-state flow first, then composes a narrative handoff that the next session in this project auto-loads (until a newer handoff or state update supersedes it). |
Outside the CLI (e.g. the desktop app), typed plugin commands aren't supported — just ask in plain words ("where am I?", "update the project state", "are we still on track?") and Claude invokes the matching skill.
No magic — just the difference between state that lives in a file and state that lives in a scrolling context window:
| Moment | With claude-state-drift | Without |
|---|---|---|
| Session start | Orientation block from your real project state | Cold start; you re-explain or the agent re-derives |
| 40 prompts in | Objective re-injected on a cadence; still in context | Goal relies on whatever survived context compaction |
| After a milestone commit | Nudge to record the transition in state.json |
Project state lives only in git archaeology |
| Next week's session | Picks up exactly where the file says you left off | Reconstruction from memory and scrollback |
A tool you add to every session only earns its keep if it isn't itself context bloat. It isn't — and almost all of the cost is paid once, at session start:
From a small demo project — the orientation block scales with your state.json size; the typical range is below.
| Injection | When | Typical size |
|---|---|---|
| Orientation block | once, at session start | ~550–2,800 tokens |
| Focus re-injection | every 6th prompt (tunable) | ~180 tokens |
| Handoff embed (only when one is parked and still fresh) | once, at session start | body capped at 6 KB |
| Handoff nudge (only at high context use) | at most once per session | one line |
For a typical long session that's roughly 1,000–4,500 tokens total — about 1–2% of a 200K window — and most of it is the one-time orientation block, which prompt-caches with the rest of your session prefix. The only recurring cost, the focus re-injection, is smaller than a single file read.
Two things keep it bounded:
- The re-injection stays small —
current_focusis length-capped, and the objective is your one-line master vision. - The orientation block scales with what you put in
state.json— a one-sentencecurrent_focuskeeps it near the low end. You're in control.
Tune or disable the per-prompt focus check per project in .claude/hooks-config.json,
or machine-wide in ~/.claude/hooks-config.json (see SCHEMA.md); the
session-end staleness nudge has its own STATE_STALENESS_* environment switches. Nothing is measured remotely: the optional
CLAUDE_HOOK_LOG=1 writes a local .claude/.hook-log.jsonl and nothing leaves
your machine. For scale, that per-session cost is in the same range as a lean
project CLAUDE.md, and a small fraction of what one MCP server's tool
definitions cost you on every turn.
CLAUDE_HOOK_CAPTURE=1 (off by default) writes each hook's raw input payload
to .claude/.hook-captures/ in the current project — useful when regenerating
payload fixtures. Captures are written locally only, the directory self-ignores
in git, and nothing leaves your machine.
(Token counts measured with a GPT-family tokenizer as a proxy; Claude's own
tokenizer differs by ~±15%. Numbers are for typical projects — a verbose
state.json costs more, which is why the current_focus field is meant to stay
short.)
This plugin's own release pipeline was built while running the plugin — every
session opened by its orientation block, its objective re-surfaced by its own focus-check.
The repo was built across a six-phase, 69-commit milestone (June 6–11 2026)
with every session tracked in state.json by the tool — and has been dogfooded
through every release since (70+ deliverables tracked and counting):
timeline
title Six phases in six days — tracked in state.json the whole way
2026-06-06 : Curation build engine : Content transforms + gates
2026-06-07 : Emitted-cut validation : Clean-install acceptance
2026-06-10 : Public repo + regeneration-drift CI
2026-06-11 : v0.1.0 published
That's heavy real-world use — a dogfooding record, not a controlled efficacy claim.
Operational numbers from the author's own machine — every repo with the opt-in
CLAUDE_HOOK_LOG=1 telemetry enabled, 2026-06-12 → 2026-08-16 (~9 weeks,
8 repos), computed by the same local pipeline behind where-am-i --stats:
| Measured | Value |
|---|---|
| Sessions opened with an orientation block | 458 |
| Orientation blocks injected | 836 |
| Focus re-injections | 428 |
| Update nudges fired (commit + staleness) | 29 |
| Orientation cost — median, per repo | ~550–2,800 tokens |
| Focus re-injection cost — median | ~190 tokens |
Two honest footnotes:
- Token numbers are the tool's own estimate (injected bytes ÷ 3.8, within ~±15% of a tokenizer count).
- One of the eight repos let
current_focusgrow into a ~9,000-token orientation block — the documented failure mode from the cost section above. The fix is editorial, not configuration: keep that field to a sentence.
These are activity and cost receipts — what fired and what it cost. Deliberately
absent: any "drift prevented" or nudge-conversion number. A conversion rate
exists in --stats, but at ~30 fleet-wide nudge events it is noise, not signal
— and no activity telemetry can observe the counterfactual anyway.
The whole system revolves around one file, .claude/state.json:
objective— the master vision; rarely changes.current_focus— one sentence on what you're doing right now.deliverables[]— units of work, each with astatus(done/in_progress/deferred/blocked).open_questions[]— unresolved decisions, so they resurface instead of getting lost.blocked[]— work waiting on something external.
See SCHEMA.md for the full schema, a copy-paste starter file, and the
per-project .claude/hooks-config.json knobs.
The plugin also bundles a few CLI tools, all on the Bash tool's PATH in any
session while the plugin is enabled — just ask Claude to run them:
state-validate— schema-check astate.json(exit0= valid).where-am-i— print the orientation block on demand (--history <id>shows a deliverable's transition log;--statsshows this project's own telemetry — cost, activity, and nudge→update conversion — computed locally from the opt-in hook log, whenCLAUDE_HOOK_LOG=1).state-history— append an entry to the per-project transition log.state-handoff— park (write) and render (read --for-orient) the per-project session handoff that the orientation block embeds (see SCHEMA.md for the file format and supersession rule).state-clean— keepstate.jsonlean: archive olddonedeliverables into an append-only.claude/state-archive.jsonl(dry-run by default;--applyto write;--keep N/--older-than DAYStune it). Lossless — git and the archive are the backstop.workflows— a cross-repo board: one row per project with astate.json(walks~/devby default; override with$WORKFLOWS_ROOT).
- Claude Code with plugin support.
bash,git,jq, and Python 3 (found automatically aspy,python3, orpython— no configuration needed).- CI-verified on Linux and Windows (git-bash). macOS is expected to work (the hooks are POSIX bash) but is not currently CI-covered.
- No orientation block at session start? Your project has no
.claude/state.json(the plugin stays silent rather than nagging) or the file is invalid — ask Claude to runstate-validate(bundled, on the BashPATHwhile the plugin is enabled). - Focus-check fires too often / not often enough? Set the cadence in
.claude/hooks-config.json(per project) or~/.claude/hooks-config.json(machine-wide) — see SCHEMA.md. - Handoff nudge never fires? Without a local session-status integration the
hook has no exact context %, and the transcript-size fallback is off until you
arm it — it has no safe default because the hook can't know your context-window
size. Set
handoff_nudge_tokensin~/.claude/hooks-config.json:150000nudges around 75% of a 200K window,750000around 75% of 1M. If it fires too early or too late, your sessions are probably running a different window than the value assumes — re-arm with the matching number. - Does anything leave my machine? No. All signals are computed from local files and local git; there is no network access, and nothing is sent anywhere.
MIT — see LICENSE.
File issues here — they're read and acted on. This repo is generated: every
byte is built from a pinned source commit (see .build-provenance) and verified
byte-for-byte by CI on every push. Fixes land in the source and ship in the next
release, which is why pull requests can't be merged directly.


