Skip to content
Back to skills

Rotate

ASecurity

Rotate the current cs conversation - write a lineage-stamped handoff to .cs/handoffs/, arm it, and tell the user to run /clear so a fresh conversation continues from it. Invoke when the user asks to rotate, or accepts a context-heavy rotation suggestion.

  • 45 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added August 30, 2026
toolsrustgogitapi

Works with

  • claude code
  • api

Security analysis

A100/100

Scanned October 7, 2026

npx -y skills add hex/claude-sessions --skill rotate --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Rotate?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Rotate
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/hex-rotate/badge)](https://www.skillsdirectory.com/skills/hex-rotate)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
model: opus
name: rotate
description: Rotate the current cs conversation - write a lineage-stamped handoff to .cs/handoffs/, arm it, and tell the user to run /clear so a fresh conversation continues from it. Invoke when the user asks to rotate, or accepts a context-heavy rotation suggestion.
---

Rotation ends this conversation's useful life deliberately: you distill the
work into a handoff file and arm it, and the next fresh conversation — most
easily one the user starts with `/clear`, without leaving Claude Code —
continues from it. This skill writes the handoff and the pending marker. It
never ends the conversation and never launches anything.

## Prerequisites

Only works in a cs session: check that `$CLAUDE_SESSION_NAME` is set. If
empty, tell the user rotation needs a cs session and stop.

A rotation needs a purpose — one line describing what the next conversation
should do. If the user did not give one, take it from the conversation: the
work in flight and its next step. Do not stop to ask; the `cs` mod's
button runs `/rotate` with no argument, and a question there would defeat the
one-key rotation it exists for.

**Encrypted session.** When `.cs/private` exists, the session keeps its
handoffs in its vault: every `.cs/handoffs/` below means
`.cs/private/handoffs/`, step 9 arms `.cs/private/pending-handoff`, and the
session log is `.cs/private/session.log`. Commit nothing from the vault:
skip steps 4, 6 and 8 for handoffs (the prune's "git tracks it" rule then
keeps every one). If `.cs/private` is a link to a missing directory, the
vault is locked: tell the user to mount it and stop.

## Process

1. Determine the parent conversation UUID: the `claude_session_id` line of
   `.cs/local/state`, or if that is missing, `$CS_CLAUDE_SESSION_ID`.

   Take the state file first. `CS_CLAUDE_SESSION_ID` is the *launch* UUID —
   cs exports it once per process and never refreshes it, because the
   SessionStart hook keys its ref-rename guard on that value still naming
   this process's predecessor. The hook rebinds `claude_session_id` on every
   fresh conversation, so the two agree only until the first `/clear`. After
   that the env var names an ancestor, and `parent:` must be the conversation
   that is writing this handoff — step 7 supersedes by matching it against
   the session log.
2. Pick a short kebab-case slug from the purpose (e.g. `continue-f5-plan`).
3. Write `.cs/handoffs/YYYY-MM-DD-<slug>.md` (today's date; create the
   directory if missing) with EXACTLY this frontmatter, then the body:

   ```
   ---
   parent: <parent-uuid>
   created: <ISO-8601 UTC timestamp>
   purpose: <the one-line purpose>
   status: unconsumed
   ---
   ```

   The body has nine sections, in this order, each under its numbered
   heading: 1. Next Step; 2. Settled and rejected; 3. Conversation-only
   facts; 4. Primary Request and Intent; 5. Key Technical Concepts; 6. Files
   and Code Sections (with the snippets that matter); 7. Problem Solving;
   8. Pending Tasks; 9. Current Work. Write for a successor with zero
   conversation memory. A section with nothing in it says `none` rather
   than disappearing: an empty section stated is a claim, a missing one is
   indistinguishable from a section the writer ran out of context to fill.
   Under cs the native task list is keyed to the session, not to this
   conversation (`CLAUDE_CODE_TASK_LIST_ID` is the session name), so it
   survives the `/clear` and the successor inherits it. Pending Tasks still
   lists every open native item with its status: the handoff has to read
   whole on its own, and the successor reconciles the list it inherited
   against what you wrote rather than mirroring the handoff into it.

   **Build the ledger before you write any prose.** A handoff is only as
   good as the facts that survive into it, and a writer who summarises from
   memory keeps whichever facts happen to be vivid. So the first thing you
   do is not writing: go back over the conversation from its first message
   to its last, in order, and at every user message and every tool result
   copy out each item below that it contains, except detail the Redact rule
   below names, which enters the ledger as a pointer. Copy, do not paraphrase: the
   successor will quote your ledger back as an answer, and a number
   rounded or an error reworded is a wrong answer.

   - **IDs**: run, job, task, PR, commit and conversation identifiers;
     branch, host, pid, port, tmux target; every path the conversation
     created or depended on.
   - **READINGS**: every number observed: counts, scores, sizes, timings,
     percentages, versions, exit codes, and the moment it was read at.
   - **ERRORS**: every failure, as the command that failed plus the text it
     printed, verbatim, trimmed to the line that matters.
   - **USER**: every request, ruling, correction, preference and answer the
     user gave, quoted in their own words, with what it answered. Keep them
     close to their own words; a correction paraphrased is a correction
     drifted, and the successor has no way back to the original.
   - **DECISIONS**: every choice made, every alternative
     rejected with the reason they lost, every approach tried that
     failed and the symptom it failed on.
   - **UNVERIFIED**: every claim the conversation relied on but never
     checked, every inference from a symptom, every "should" and "probably".
   - **STATE**: what is running, where, and how to tell whether it finished;
     what is uncommitted; what was promised and not done.

   Then check each one off as it lands in the body: DECISIONS go to Settled
   and rejected; IDS, READINGS, ERRORS, USER and UNVERIFIED go to
   Conversation-only facts; STATE feeds Next Step and Current Work. Nothing
   in the ledger may be dropped for length. The ledger is the part of the
   handoff that nothing else can recover; spend length on it and
   keep everything else concise.

   **1. Next Step opens the body.** The successor is told to execute it, and
   retrieval degrades over a long document, so the thing it needs first must
   not be the thing it finds last. It carries every fact its first action
   needs, inline: the exact command, path, host, branch or flag, even when
   the same fact is in a committed file, an older handoff or memory, because
   the successor acts on it without looking anything up. When the first
   action cannot start until the user answers, decides or supplies
   something, the blocker opens Next Step: the question to ask and the
   options already on the table, ahead of the steps it gates, and those
   steps written as branches on the answer, not a list to run through. A
   blocker listed after those steps reads as one more step. An action that
   starts work (a test run, a build, a merge, a deploy) first says how to
   tell whether it is already done or still running, and what to do in each
   case. An action that needs a clean worktree (a branch switch, a merge, a
   rebase) says the successor starts with this handoff uncommitted, because
   session start flips its `status:` to `consumed`, and says to commit that
   change first.

   **2. Settled and rejected** is the DECISIONS ledger, one line each:
   `<decision or rejected alternative> | <who decided, with the user's words
   when it was the user> | <reason, or the symptom it failed on>`. A commit
   carries what was done and never what was rejected, so a successor without
   this re-opens settled questions. Write `none` when there is nothing.

   **3. Conversation-only facts** is the rest of the ledger, grouped under
   the category names above (IDS, READINGS, ERRORS, USER, UNVERIFIED), one
   fact per line, each line starting with its provenance label:

   - `measured:` you ran it and saw the result; give the command and the
     reading.
   - `read in source:` give the path (and line) you read it from.
   - `inherited:` from a prior handoff, memory or a reviewer; name which.
   - `user:` the user said it; quote them.
   - `assumed:` everything else, including every UNVERIFIED line and every
     cause inferred from a measured symptom.

   Say how each claim was established: a claim read off a README and a claim
   measured live look identical to a successor, and it will build on both
   equally. The label goes on the claim, not on the bullet or heading it
   sits under. A claim that something fails, is unavailable or does not work
   carries the command that failed and what it printed; without them it is
   `assumed`. When you cannot recall which label fits, `assumed` is the
   honest answer and costs nothing. Write `none` for an empty category.

   Four rules govern the body, all following from where it goes: step 4
   commits it, and the next conversation reads it as its opening prompt.

   - **Redact.** API keys, tokens, passwords, personally identifying
     information, anything the session deliberately left untracked for
     privacy, and other sensitive personal detail (such as health, medical,
     body measurements or finances) stay out of the file, including out of
     the verbatim ledger. Redaction outranks every rule that pulls detail
     in: the verbatim ledger, the USER bullet and Next Step's inline rule.
     Quote the user's ruling and replace the sensitive part with a pointer
     to where it lives ("pain history: in the untracked `.cs/plan.md`"). A
     first action that needs the redacted detail says to read it from that
     path first, and if the file is absent on this machine, to ask the user
     before proceeding as if there were no constraint.
     `.cs/handoffs/` is tracked, so writing one here publishes it;
     credentials live in `cs -secrets`. Name the secret's purpose instead:
     "the deploy token, in `cs -secrets get DEPLOY_TOKEN`".
     Re-read the finished body before step 4 commits it, for secrets and
     for the detail this rule names: an exact reading is where a secret
     hides, and a verbatim quote is where personal detail does.
   - **Reference committed work; restate what a successor cannot recover.**
     Work captured in a commit, spec, plan, diff or narrative gets a path and
     a one-line pointer in sections 4-9, never a re-summary. The ledger is
     the opposite case: it holds only what has no path to point at. A
     pointer to a script or command says in one clause what it does when
     run, read from the script itself rather than remembered.
   - **Sections 4-9 are short.** Each is a handful of lines of pointers and
     conclusions. Your own explanations and reasoning condense to what they
     concluded; the user's words do not condense.
   - **Say what you could not carry.** If you are working from compacted
     context, or you cut anything short, say so in the handoff: a thin
     handoff that admits it is thin beats one the successor trusts.

   Write the body in TWO passes, and make the first one durable. Rotation
   runs when context is already hot, and a compaction can land before you
   finish. The first Write carries the frontmatter, Next Step, Settled and
   rejected, and Conversation-only facts: the ledger, which dies with the
   conversation. Commit that (step 4) before continuing. The second pass
   then APPENDS sections 4-9 (step 5), recoverable from the repo if this
   rotation never finishes, and lands as a second commit. Append, never a
   second Write: a Write replaces the whole file and re-emits pass one from
   whatever context you have by then. Never rewrite the first commit
   either; it is the only faithful copy of pass one.
4. Commit the first pass. Stage the handoff by name. Re-read the body for
   secrets first (the Redact rule above) — the same re-read runs again before
   step 6, because pass two quotes files and code, and a secret can sit in
   either.

   Before staging any path in steps 4, 6 or 8, run
   `git check-ignore -q --no-index -- <path>`, one path per call (`-q` takes
   only one). Exit 0 means the repo ignores it; a repo that ignores `.cs/`
   does so to keep session notes out of a public repo. Never `git add -f`
   an ignored path, even when a plain `git add` fails on it: skip that commit,
   leave the file on disk, and say so in your reply. The handoff still works
   uncommitted; step 9 arms it by basename.
5. Append the second pass with Edit or `cat >>`, never Write.
6. Second commit for the appended body, unless step 4 found the handoff
   ignored. Re-read it for secrets and the detail the Redact rule names first.

   Once both commits land, the handoff's CONTENT is safe: nothing left to
   lose. It is not yet armed — that is deliberate, see step 9.
7. Retire this machine's leftovers: for every OTHER file in `.cs/handoffs/`
   whose frontmatter still says `status: unconsumed`, flip that one
   frontmatter line to `status: superseded` — but only when its `parent:`
   UUID appears in `.cs/local/session.log` (`.cs/private/session.log` when
   `.cs/private` exists: an encrypted session), which records every
   conversation this checkout has run (`Session started (... ID: <uuid>)`).

   That file is machine-local, which is the whole point of using it.
   `.cs/handoffs/` is shared, so a handoff whose parent is absent from the log
   belongs to a co-worker's checkout, may be armed right now, and must be left
   alone — superseding it would silently drop their rotation.

   Without this step an abandoned handoff stays pending forever, and the
   launch prompt keeps offering `[Y/n/r/d]` for context that is out of date.
   Do not assume a newer handoff simply outranks it: among files the launcher
   has to choose between, it picks the lexicographically last basename, so
   among same-day files the slug decides and a stale one can win. The marker
   step 9 arms is the exception — it names one handoff explicitly and the
   launcher honors it over that scan — but it covers only the file this
   rotation is arming, not the leftovers this step retires.

   Then prune what is spent. A `consumed`, `discarded` or `superseded` handoff
   has done its job, and git history keeps it after the file is gone, so
   nothing is lost by dropping it. Delete one only when all five hold:

   - its `status:` is one of those three — never `status: unconsumed`, which
     may be a co-worker's armed rotation and is not yours to drop;
   - its `created:` date is more than 30 days before today;
   - it is not among the 10 newest handoffs in the directory by `created:`,
     counting every handoff whatever its status, so a week of heavy rotation
     never empties the store;
   - it has no uncommitted changes (`git status --porcelain -- <file>`
     prints nothing). The conversation that consumed a handoff appends its
     `## Successor report` to it, and git history keeps only what was
     committed;
   - git tracks it (`git ls-files --error-unmatch -- <file>` exits 0).
     Where `.cs/` is gitignored, `git status --porcelain` prints nothing
     for a handoff git never saw, so the uncommitted-changes check passes
     it, and deleting it deletes the only copy.

   Take the age from `created:` in the frontmatter, never the file's mtime.
   `.cs/handoffs/` is shared, and a clone stamps every file with its checkout
   time: mtime would read as "all new" on a fresh machine and prune nothing,
   while saying nothing about when the handoff was written. Stage the
   deletions with step 8's commit.
8. Commit the supersedings, every consumed handoff whose uncommitted change
   is a `## Successor report` appended by the conversation that took it
   over, and any tracked session state, like narratives. Stage those paths
   by name, skipping every ignored one (the step 4 check).
9. Arm it, LAST: write its basename (no path) to `.cs/local/pending-handoff`
   (`.cs/private/pending-handoff` in an encrypted session).
   Machine-local state — never commit it.

   Arming is the final step because an armed marker is fragile in a way a
   committed file is not. If Claude Code exits before the ritual finishes and
   the user relaunches, `cs <name>`'s prompt disarms the marker on `Y`, `n`
   or Enter (lib/75-launch.sh), and nothing re-arms it; a later `/clear`
   then opens a bare conversation with this handoff left `unconsumed`. The
   launch prompt recovers either state — it scans the store and offers an
   unconsumed handoff with `r` whether or not it was armed — but only the
   marker makes `/clear` continue, and only an interrupted ritual leaves a
   disarmed one behind. So arm once nothing remains that could be
   interrupted.
10. Tell the user what the rotation now does on its own: the fresh
   conversation picks up this handoff and begins its next step by itself, a
   couple of seconds after the `/clear` — they do not need to type anything
   to start it, and a message they do send takes precedence over the handoff.

   If they would rather stop for the day, exiting and answering `r` at the
   next `cs <session-name>` launch does the same thing. Answering `Y` or `n`
   there disarms the marker (the handoff itself stays pending, so a later
   rotate can re-arm it), and `d` discards the handoff outright.

11. End your response with the instruction and nothing after it, on its own
   final line, exactly:

   **Run `/clear` now** (or press the capsule above the prompt, or Ctrl+X 1 if you bound it) — this conversation is ready to rotate.

   This is the one step you cannot take for the user. A hook cannot submit
   to Claude Code's command queue (it accepts the TUI's own input only); the
   `cs` mod's button can, and once the marker is armed it reads
   `/clear and continue from the handoff`. The keystroke is theirs unless
   they launched with `CS_ROTATE_FORCE_CTX`, when the mod counts twenty
   seconds down and runs the `/clear` itself — which is why the line must not
   end up buried under a summary of what you just wrote.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.