Releases: ramazanpolat/claude-playbooks
Release list
v3.27.0 — experimental OpenShell sandbox backend; github:owner/repo#ref pins
Highlights
An OpenShell sandbox backend (--sandbox=openshell)
Experimental, Linux with Docker Engine only, and opt-in. The default
sandbox stays sbx (Docker Sandboxes), and nothing changes unless you ask
for OpenShell. It needs a Linux host with Docker Engine 28+ and
NVIDIA OpenShell 0.1.2 or a later
0.1.x, set up once as the sandbox guide shows: telemetry off, host mounts
allowed, linger on. On macOS use sbx, or --sandbox-host to a Linux
machine. OpenShell itself is young (0.1.0 shipped on 2026-09-25).
cpb run --sandbox=openshell sre # this folder is the workdir
cpb run --sandbox=openshell --mount ~/shared-libs:ro sre # one more directory, read-only
cpb run --sandbox=openshell --sandbox-fresh sre # throw the sandbox away firstWhat the backend does (#152, on the seam from #151):
-
The sandbox: a container confined by Landlock and seccomp, with no
network unless a rule allows it. cpb writes the policy:- the working directory and the playbook's directory, mounted at their
own paths (:roread-only); - the session running as your user;
- Claude Code's hosts allowed for the claude binary only.
- the working directory and the playbook's directory, mounted at their
-
The image: built once on first use. The base is pinned by digest, and
Claude Code is pinned to 2.1.285 or to[sandbox] claude_version. -
Your keys stay outside, as with
sbx:- each key is a provider bound to its endpoint, and the sandbox sees only
OpenShell's placeholder; - a key sent to another host is refused;
- rotating the key in the env set reaches a running sandbox, and removing
it revokes the mapping; - a key that cannot be registered refuses the launch (fail closed, as in
v3.26.0).
- each key is a provider bound to its endpoint, and the sandbox sees only
-
It costs nothing while idle: a stopped sandbox is started on reuse,
and stopped again after its last session. -
A preflight refuses, in one line naming the fix, when:
- the host is not Linux;
openshellis missing, or outside 0.1.2..0.1.x;- Docker is older than 28;
- you are running as root;
- the gateway is not up, or does not allow host mounts.
--cloneandshare_skillsaresbx's, and are refused. -
Selected with the flag, or
[sandbox] backend = "openshell"in a
manifest. No grammar clause in this release.
Tested end to end on a Linux test machine (OpenShell 0.1.2, Docker 29.8.1,
Ubuntu 24.04): 27 checks, including a real claude -p and the Claude Code
TUI through the sandbox, the placeholder, the mounts, rotation, revocation and
each refusal. Known OpenShell 0.1.x behaviour cpb works around is in the sandbox
guide's OpenShell section.
Pin a github marketplace to a branch or tag (#154)
ALTER PLAYBOOK
ADD MARKETPLACE kommander FROM 'github:owner/repo#v1.2.0'; -- or @v1.2.0
- cpb passes
owner/repo#v1.2.0toclaude plugin marketplace add, the
form Claude Code itself writes. #and@spell one source, and applying again changes nothing.SHOW CREATEwrites it back, andAPPLYof that output changes nothing.- A commit cannot be pinned. Claude Code clones a marketplace by branch
or tag only, so agithub:ref that looks like a commit (7 to 40 hex
characters) is refused before anything runs. - A git URL's
#<ref>that looks like a commit is still accepted, as before,
and now warned about (marketplace_ref_not_cloneable). github:owner/repowithout a ref is unchanged.
Internal
- The drift monitor watches the stable line (#153). The nightly runs the
full arena on main and on the newestrelease/v*branch. Before, it ran on
the latest release tag, which the arena never runs, so it read as green.
It now fails when a dispatched run's full arena is skipped or missing. - The sandbox backend seam (#151): the backend owns its placeholder and
its revoke. There is no user-visible change, and the wholesbxcall log
is pinned by a golden test.
Changes a result
Nothing changes a result. Everything is additive:
- a backend value,
openshell, with its[sandbox] backendkey; - a
github:source form; - a warning code,
marketplace_ref_not_cloneable.
No word became reserved, and the upgrade from v3.26.0 is tested. The sbx
backend behaves exactly as in v3.26.0.
Docs:
- the sandbox guide's "OpenShell backend (experimental, Linux)";
- example 20 (a playbook in an OpenShell sandbox);
- SPEC-v4's "OpenShell backend (experimental, v3.27.0)";
- the reference's marketplace sources and warning codes;
- example 07 (a marketplace pinned to a tag).
Verification
- The tag commit is
ab36472(ab36472), the merge
of #155 on main, with package.json 3.27.0. - A full arena regression (phase 2) is green on the tag commit: run
36833289658, on ab36472: 21 oracles, 165/165 assertions. - CI: green for #151, #152, #153 and #154 on Ubuntu and macOS (Go 1.26),
theupgradejob included. - Arena:
- OpenShell e2e on the Linux test machine: 27/27 (
tests/openshell-e2e.sh). - Review:
- Upgrade: from v3.26.0, via the CI
upgradejob on the tag commit
(CI run 36832102685, green on Ubuntu and macOS). - The OpenShell e2e used dummy tokens, a throwaway HOME and a fake Anthropic
endpoint. Everything else ran on throwaway playbooks and made-up stores.
v3.26.0 — security: the sandbox refuses a key it cannot protect; SELECT --json in query order; npx falls back
Highlights
A sandbox that fails closed (security fix). By default a sandboxed
launch keeps your backend API keys (ANTHROPIC_API_KEY,
ANTHROPIC_AUTH_TOKEN) outside the sandbox. The sandbox sees a placeholder,
and the host-side proxy puts the real key into requests to that endpoint only.
Until now, if cpb could not register a key with the proxy, it printed a
warning and passed the real key into the sandbox as a plain variable. Now
the launch stops (#149, fixes #148):
ANTHROPIC_API_KEY could not be registered at the sandbox proxy for api.anthropic.com
(… --value <redacted> …), so the launch stops: the key would otherwise enter the
sandbox as a plain value. Retry, or set [sandbox] secrets = "env" to pass keys into
the sandbox as plain variables
- It stops before anything is attached, and names the key and the host,
never the value. - The backend's own error is kept for diagnosis, with the key's value
replaced by<redacted>. [sandbox] secrets = "env"is now the only way a key enters the sandbox as
a plain value.
A refused sandboxed launch gives your login back. On the shared-login
path, a sandboxed launch points the playbook's .credentials.json at a
login kept inside the sandbox. The link was put back only when a session
ended. A launch refused before its session left the link pointing into the
sandbox until the next host launch repaired it. That happened on a mount
check, a failed create, the creation-marker check, and now a failed key
registration. Every way out of a sandboxed launch now restores it.
SELECT … --json keys follow your query (#144, fixes #133).
cpb "SELECT version, name FROM PLAYBOOKS" --json now prints version
before name, the order clickhouse local writes, so the built-in path and
ClickHouse agree. They used to come out sorted.
- A column named twice is one key, at its first position.
- Values, the set of keys and nested objects are unchanged.
npx keeps working between a release's merge and its tag (#145, fixes #142).
When package.json names a release that is not published yet,
npx github:ramazanpolat/claude-playbooks now runs the newest published
release and says so:
cpb v3.26.0 is not published yet; running v3.25.0
- The fallback happens only on an HTTP 404 for the package version's binary.
- It never falls back to a release candidate (
-rcN), to another major
version, or to the missing release itself. - A version you pin with
CPB_VERSIONis never replaced. - The release check's warning now says what npx does in that window.
A README that says what cpb is for (#146). It opens with the four
things a playbook can isolate: its config home, its environment, its login
and its process (the sandbox). Then it has one section per use, and what
makes cpb reliable. Every claim is narrowed to what the reference and guides
promise.
Internal: the sandbox backend seam (#151): the backend owns its
placeholder and its revoke, in preparation for a second backend. No
user-visible change; the whole sbx call log is pinned by a golden test.
Changes a result
- A sandboxed launch that used to warn and go ahead now refuses when a
key cannot be registered at the proxy. If that is what you want, set
[sandbox] secrets = "env", which passes keys in as plain variables. SELECT … --jsonkey order now follows the query instead of being
sorted. Any JSON parser reads the same data. Only a consumer that compares
the raw text, or depends on key order, sees a difference.
Nothing else changes a result. No word became reserved, no --json field
changed meaning, and the upgrade from v3.25.0 is tested.
Docs:
- the sandbox guide lists when a key does enter the sandbox;
- SPEC-v4 states the refusal and the restore;
- the SELECT section of the reference states the key order;
- the installation guide's npx section describes the fallback;
- the README.
Verification
-
The tag commit is
38ffb55(38ffb55), the merge
of #150 on main, with package.json 3.26.0. -
A full arena regression (phase 2) is green on the tag commit: run
36704481473, on 38ffb55: 21 oracles, 163/163 assertions. -
CI: green for #144, #145, #146, #147, #149 and #151 on Ubuntu and macOS
(Go 1.26), theupgradejob included. -
Arena: cli-grammar gains
select-order-okand
sandbox-secret-refuse-ok(a stubsbx). Each fails against the code
before its fix. Targeted runs:PR Run Result #144 36572366533 43/43 #145 36573039590 42/42 (the shim is not on the arena's path) #149 36697362717, then 36699624794 on the final head 44/44, 44/44 The full phase 2 on the tag commit is listed above.
-
Tests:
TestSelectJSONKeyOrdercovers every table's columns reversed;.github/scripts/npx-shim_test.shhas 33 checks with a stubcurl, on
both OSes;- the sandbox refusal test uses a canary key that must appear in no
message, and nosbxcall but its registration; TestRunSandboxRefusalRestoresSharedLogincovers a failed registration
and a failed create.
Each test fails against the code before its fix.
-
Review:
-
Codex (once per PR):
-
Antigravity (Gemini 3.8 Flash): one round per PR. Its valid points
were fixed: #144's vacuous repeated-column arena check, #145's vacuous
rc test, and four README precision points on #146. The rest were
answered on the PRs. On #149 the first attempt was blocked by Gemini's
filters, and the retry's one point was answered with evidence.
-
-
Upgrade: from v3.25.0, via the CI
upgradejob on the tag commit
(CI run 36703676380, green on Ubuntu and macOS). -
Everything ran on throwaway playbooks, a throwaway HOME, stub
sbxand
curl, and made-up keys.
v3.25.0 — cpb tui, sessions and RESUME, status line history and panels
Highlights
cpb tui: a terminal UI over the grammar. It is read-only in v1.
cpb tui
- Browse:
- your playbooks, with their login kind and live-session count;
- a playbook's tabs: Overview (with the pilot profile line), Env, Vars
(effective at launch, with the layer each comes from), Plugins, MCP,
Skills, Status line, Model, Sessions; - the live sessions, with pid, terminal, folder and model, and this
folder's recent ones; - env sets and defaults.
- Take what you see with you:
cshowsSHOW CREATE(without secrets);ycopies the statement behind the selection;eexports the selection as<name>.cpb, a recipecpb APPLY
re-applies. It asks before replacing a file, and writes atomically.
- Resume:
enteron a recent session that is not running resumes it
through its playbook, ascpb RESUMEdoes, and comes back when you leave
Claude. - A front-end, not a second engine. Every screen is a
cpb … --json
statement, named on its last line, so anything you see can be scripted.
No secret value is ever shown. - The terminal is always given back: on
q, Ctrl-C, a kill, a closed
window or a crash. - Plain
cpbin a terminal ends with the hintBrowse and manage them: cpb tui.
No cost for everything else.
- Built on: bubbletea v2 (
charm.land/bubbletea/v2v2.0.10, bubbles
v2.1.1, lipgloss v2.0.6), pinned. - Startup:
cpb SHOW PLAYBOOKSstarts in 24 ms against 23 ms before,
and sends the terminal nothing. - Why bubbles is held at v2.1.1: newer bubbles pull in a go-runewidth
whose startup code costs about 48 ms in every cpb command and launcher. - Two permanent tests fail the build if a dependency ever makes cpb
query the terminal at startup, or spend more than 5 ms initializing a
package.
Additive only: the command tui and the hint line, which appears on a
terminal only. Off a terminal, bare cpb prints exactly what it did.
Size: the binary grows about 2 MiB (+1.96 MiB on linux/amd64, +1.89
MiB on darwin/arm64, stripped).
Docs:
- the reference section "cpb tui (v3.25.0)";
- the guide
docs/guides/tui.md, whose screens come from the tests; - example 19;
- a README line.
The status line, composed: panels for a status line host, and a way
back. This is the first minor after the stable v3.24.0. Everything in it
is additive: every v3.24.0 statement, file and --json field keeps its
meaning.
ALTER PLAYBOOK kommander
SET STATUSLINE '"$HOME/.local/bin/statusmux" render' REFRESH 10
ADD PANEL local.clock EXEC 'date +%H:%M' ALIGN RIGHT
ADD PANEL local.model TEMPLATE '{model.display_name}'
ADD PANEL kommander.beat OBSERVE 'sh ${PANEL_DIR}/beat.sh' EVERY 10000;
- Status line panels:
ADD PANEL <ns>.<id>andDROP PANEL.- A status line host such as
statusmux holds Claude Code's
one status line slot and composes the bar from panels. cpb writes those
panels as SPC/1 manifests, at
statusline.d/<ns>/<id>.toml. - There are four types:
EXEC(a command on every render),TEMPLATE
(text from Claude Code's JSON),RECORDS(a file another process
keeps) andOBSERVE(a side effect such as a heartbeat, never shown).
Their options (ROW,PRIORITY,ALIGN,TIMEOUT, …) are the
manifest's fields. ADD PANEL local.bar FROM STATUSLINEturns the bar you have now into a
panel, before you put the host in its place.- Yours stays yours. cpb never writes the layout file
(statusline.toml) or a plugin's panels. A manifest carries a hash of
its own content, so once you edit one cpb wrote, cpb treats it as yours
and never overwrites or removes it. - No credentials by accident. A panel ships with the playbook, so a
credential-looking value in a command or template is refused unless you
sayAS PLAINTEXT.SHOW CREATEnever prints one. - Reads.
SHOW PLAYBOOK --jsonlists the panels, with enabled
plugins' panels read-only, as doesSELECT … FROM PANELS.EXPLAIN
says when the status line is not a host, so the panels would not render.
- A status line host such as
SET STATUSLINE PREVIOUSputs back the status line a statement
replaced last,paddingandrefreshIntervalincluded. Twice toggles
back.- cpb keeps a short history (10 per directory) in its own state,
.state/statusline-history.json. SHOW --jsonandSELECTshow the history, asstatusline_history.
- cpb keeps a short history (10 per directory) in its own state,
Sessions: see them and resume them in the right playbook. (#132)
cpb sessions(short forSHOW SESSIONS) lists the live Claude Code
sessions of every playbook. It shows the pid, folder, age, last activity,
model, and the command that resumes each one.SELECT … FROM SESSIONS
queries them.cpb RESUMEresumes the newest session in this folder that is not
running, through its playbook's own launch. It says which one it took and
which live ones it skipped.RESUME SESSION '<id>'works from anywhere,
andRESUME --listlists them.- A live session is never resumed: two processes on one session id
corrupt it. - After a session ends under a launcher, on a terminal, cpb prints
Resume this playbook's session with: <launcher> --resume <id>. Claude Code's own
line would look in~/.claude. - How cpb knows. cpb reads Claude Code's own per-process session files.
It reads no other process's environment, and never changes those files. - Where each session runs:
SHOW SESSIONSshows each session's
terminal, astty(pts/3,ttys012), or null for a background
session. It comes from/procon Linux andpson macOS. (#134)
Is the pilot profile imported? SHOW PLAYBOOK --json has
pilot_profile, which is imported, not_imported or unknown.
- It is read from the import line in the playbook's
CLAUDE.md, never
from the profile itself. - The human form has a
Pilot profile:line, andSELECT … FROM PLAYBOOKShas the column. - Because the human form aligns its labels to the widest one present, the
new label realigns the block. The human form is not stable surface; the
--jsonfields are. (#135)
Building from source now needs Go 1.26 (was 1.21). This was the
prerequisite for the terminal UI above.
- go.mod says
go 1.26.0. - CI, the arena's
golang:1.26build and the release build all use 1.26. - The Nix flake's nixpkgs already built with Go 1.26.
- Release binaries and
npxare unaffected. (#137) - Do not change
release/v3.24(v3.24.x), which stays on 1.21.
How cpb is released (CI only, no change to cpb itself):
- A release needs a green full arena regression (phase 2) on its exact tag
commit, no older than two days. That replaces "7 green nights" (the
pilot's rule, 2026-09-29). A re-run is allowed only for an infrastructure
failure. - The nightlies are drift monitors. They run phase 2 on main and on the
latest release, and report only when red; a red on a release becomes a
patch. - Releases can come from a release branch. The release workflow
publishes a tag on main or on its own minor'srelease/vX.Y, and fails
visibly otherwise. The npx version check compares with the newest release
by version. AGENTS.md has the steps.
Additive only. The new pieces:
- clauses:
ADD PANEL,DROP PANEL,SET STATUSLINE PREVIOUS; - statements:
SHOW SESSIONS,RESUME; the commandsessions; - the tables
PANELSandSESSIONS; --jsonfields:panelsandstatusline_history;pilot_profile, the
last field ofSHOW PLAYBOOK; theSHOW SESSIONS --jsonandRESUME --list --jsonshapes, withttyas the last session field;- SELECT columns:
SESSIONS.ttyandPLAYBOOKS.pilot_profile, each last.
On the clickhouse-local path,SELECT *listspilot_profileafter the
computedversion_tuple, so every earlier position holds; - the
APPLY --jsondelete action valuepanel; - cpb state files:
.state/statusline-history.json, and the manifests
understatusline.d/.
No word became reserved. The upgrade from v3.24.0 is tested.
Docs:
- the reference sections "Status line panels", the history, "Sessions" and
"Output"; - the guide
resume-a-session.mdand example 18 (sessions); - the configure-an-agent guide;
- example 17 (a host and its panels), and example 10 now uses
PREVIOUS.
Verification
-
The tag commit is
14b7287(14b7287), the merge
of #143 on main, with package.json 3.25.0. -
A full arena regression (phase 2) is green on the tag commit: run
36563476436, on 14b7287: 21 oracles, 161/161 assertions. -
CI:
-
Arena: cli-grammar gains
statusline-history-okandpanels-ok(11
assertions),sessions-ok(11),sessions-tty-ok(3) and
pilot-profile-field-ok(4), each confirmed by root for the pilot.
Targeted runs:PR Run Result #130 36357036085 37/37 #131 36358810509 38/38 #132 36406166953 39/39 #134 36473747395 40/40 #137 36475449840 39/39, the golang:1.26bench build#135 36475931027 41/41 The full phase 2 on the tag commit is listed above.
-
Review:
- Antigravity (Gemini 3.8 Flash): two rounds on panels, where a
FROM STATUSLINEbypass of the command checks was found and fixed; one
round on each later PR. - Codex (once per PR, from #132 on): #132 found a missing cwd, an
unconfirmed procStart and a dropped dirs.toml error. #134 found tty_nr
signedness. #135 found the ClickHouseSELECT *column order. All were
fixed before merge.
- Antigravity (Gemini 3.8 Flash): two rounds on panels, where a
-
Upgrade: from v3.24.0, via the CI
upgradejob on the tag commit
(CI run 36562657793, green on Ubuntu and macOS). -
E...
v3.24.0 — the first stable release
The stable release
v3.24.0 is cpb's first stable release. Nothing new is added in it. This
is the release from which the grammar, the commands, the file formats and
the --json output are promised to stay put. It ships with the other stable
releases of the stack (Agent Kommander 1.0, pilot-profile 1.0, cockpit 1.0),
after a week of freeze. It is released from its own branch, release/v3.24:
main has moved on to v3.25.
What is frozen
The reference now has a Stability section
(docs/reference/cli-grammar.md). From v3.24.0 on:
- Breaking changes only in a new major. A statement or clause that stops
parsing, a clause whose effect changes, a--jsonfield that changes
meaning or goes away, or a file-format change an older cpb cannot read. - Additions in minor releases. A new clause, object, optional
--json
field, warning code orSELECTcolumn. New clause words are never
reserved, so a name that works today keeps working. - Deprecations warn for at least one minor release before they go, on
stderr and off a terminal too. - Fixes in patches. The notes say when a fix changes a result.
The stable surface:
- The grammar: every statement, clause and refusal, and the reserved
words. - The visible commands and their flags:
install,run,start,
update,auth status,completion,self-uninstall. - The file formats:
.playbook,.env-profiles/,.state/dirs.toml,
and thesettings.jsonkeys cpb writes. - The
--jsonshapes: SHOW, EXPLAIN,APPLY --dry-run --json
(schema 1),SELECT/DESCRIBE, andauth status --json. - The codes: the four
APPLYwarning codes and its exit codes.
The human-readable output and the wording of messages are not part of it.
Scripts should read --json.
Deprecated: the pre-grammar commands
env, env-profile, create <name>, link, delete, rename, alias,
dealias, list and info keep working, unchanged, through 3.x.
- Every use now prints one line on stderr, on a terminal or not:
Deprecated: `claude-playbook env` is removed in v4.0.0; the grammar form is: cpb … - stdout is exactly what it was, so nothing a script parses changes.
- v4.0.0 removes them. Move scripts to the statements now, and move any
parsing toSHOW … --jsonorEXPLAIN … --json. For example, the
effective value of one variable:claude-playbook EXPLAIN PLAYBOOK "$name" --json | jq -r '.vars[] | select(.key=="MY_VAR" and (.blocked|not)) | .value // empty'
The upgrade is tested
- The
upgradeCI job runs on Ubuntu and macOS. It builds the previous
release, applies that release's own examples with it, then hands the same
state to the new build. The new build must show:- byte-identical
SHOW CREATE ALL,EXPLAIN --jsonand
auth status --json; - no change on a re-apply;
- every playbook launching and dropping;
- a made-up machine login never touched.
- byte-identical
- It runs on every change from now on, and a release needs it green.
- The upgrade from v3.23.1 (the previous release) passed on the release
commit: theupgradejob on ubuntu (Go 1.21) and macOS (Go 1.26), CI
run 36485688478 on #140, whose merge is the tag commit. - The examples check (
examples/check.sh) now enforces every step of every
example. Before, three examples without their own checks
(03-defaults,05-show-create-roundtrip,06-install-from-git) had
their idempotency unchecked. All three passed once checked.
Also in this release
-
Releases from a release branch. The release workflow publishes a tag
whose commit is on main or on its own minor's release branch
(release/v3.24for v3.24.x), and fails, naming both, for any other tag;
before, it skipped publishing and stayed green. The npx version check
compares with the newest release by version. AGENTS.md has the steps. This
is CI only, with no change to cpb itself. -
A status line host keeps its slot. When the status line is
statusmux's (statusmux render), aSET STATUSLINE '<another command>'
(a recipe re-applied, say) leaves it in place and warns:
statusline_held_by_host.REFRESHstill applies.UNSET STATUSLINEis the explicit way to take the slot back.- Before, re-applying a recipe silently unwired statusmux and stopped its
observers, such as the Kommander lease heartbeat.
The security fixes since the last minor
These shipped as v3.22.1 and v3.23.1, and are listed here for anyone
arriving from an older release:
- A playbook source never carries a login (v3.22.1). Before, installing
a source that shipped.credentials.jsoncould replace your machine's
Claude login with the source's account. - A shared playbook copies only the machine's own account (v3.23.1).
Before, a login of another account made inside a shared playbook could
replace the machine's login at the next launch.
Verification
- A full arena regression (phase 2) is green on the tag commit (debb155):
run 36543441290. The pilot's rule since 2026-09-29: a stable release needs
a green full phase 2 on the exact tag commit, and a re-run is allowed only
for an infrastructure failure. Nightlies are drift monitors on main and the
latest release, and report only when red. - CI is green on Ubuntu (Go 1.21) and macOS (Go 1.26) for every PR in
the stabilization week (#124–#128). That includes the newupgradejob. - The arena: cli-grammar has 36 assertions, each added with its PR and
confirmed by root for the pilot:statusline-host-kept-ok,
hidden-deprecated-ok, and the earlier ones. - Review: Antigravity (Gemini 3.8 Flash) was the review of record on each
PR, since Codex was at quota. Every finding was fixed or answered on its
PR. - No open security issue and no known data-loss bug. The known issue
docs/known-issues/shared-launch-copies-own-login-over-machine-login.md
is fixed (v3.22.1 and v3.23.1). - Every test and check ran in throwaway homes with made-up credential
stores.
v3.23.1 — security: credential sync only within the same account
Security fix: a shared playbook copies only the machine's own login
A login of another account could replace your machine's login. Claude
Code writes its login store by renaming a new file over .credentials.json.
So a refresh or a /login inside a playbook that shares the machine's login
replaces cpb's link with a file of its own. Before v3.23.1, the next sync
copied any such file over ~/.claude/.credentials.json whenever it was
newer, without asking whose login it was. A login made as another account
then became the login of every shared playbook. That could happen during a
one-off CLAUDE_PLAYBOOKS_ISOLATE_AUTH=true launch, or in a playbook whose
isolation was removed by hand. v3.22.1 had already closed the install path.
Update: claude-playbook update, or install the binary as usual.
- cpb now compares accounts. The
accountUuidin the playbook's
.claude.jsonmust equal the machine's own, from~/.claude/.claude.json
or~/.claude.json.- The same account: the newer login is copied, as before. That is how a
refresh inside a playbook reaches the others. - Another account, or one cpb cannot confirm: the file is kept as
.credentials.json.cpb-own-<stamp>. Its account state leaves the
playbook's.claude.json, with a backup. The link to the machine's login
comes back, and one line says so. It names neither account nor any value.
To keep that account in that playbook:cpb ALTER PLAYBOOK <name> SET ISOLATED LOGIN, then move the file back.
- The same account: the newer login is copied, as before. That is how a
- Account state is taken only from the machine's own files. Before, when
those had none, cpb looked through other playbooks'.claude.json. That
could give a shared playbook the identity of a second account kept in an
isolated one. - On macOS Claude Code keeps each playbook's login in its own Keychain
item (Claude Code-credentials-<hash>), with the file as a fallback. cpb
never copies a Keychain item, so the file path above runs there only when
Claude Code falls back to the file. On Linux the file is the only store.
The authentication guide now explains both.
Were you affected? Run cpb auth status. It shows store kinds and modes,
never values. In ~/.claude, run claude auth status to check the machine's
account. A set-aside file (.credentials.json.cpb-own-*) left after
updating means a playbook held another login, which is now kept safe.
Verification
-
New tests:
- another account;
- an unconfirmed account, on either side;
- the same account (the refresh path, kept);
- no machine store;
- an end-to-end run of the one-off-isolation path;
- account state never taken from another playbook.
Each asserts the machine's store is byte-for-byte unchanged. Run against
v3.23.0, they fail with the store overwritten. -
Arena:
shared-sync-same-account-ok, on the real binary, which fails on
v3.23.0. The targeted run 36321541652 passed 34/34. -
Review: Antigravity (Gemini 3.8 Flash), clean. Codex was at quota.
-
Safety: every check used throwaway homes and made-up stores. On the
pilot's Mac, Keychain items were checked by name only; no value was read.
Release gate: arena phase 2 run 36322172575 on 7df50d7 (success).
v3.23.0 — status line refresh, NO PILOT PROFILE, ISOLATED LOGIN
Highlights
A status line that keeps rendering, playbooks for other model routes, and
a login of a playbook's own.
ALTER PLAYBOOK kommander SET STATUSLINE 'bash statusline.sh' REFRESH 10;
CREATE PLAYBOOK routed NO ALIAS NO PILOT PROFILE;
CREATE PLAYBOOK second-account ISOLATED LOGIN;
SET STATUSLINE '<command>' REFRESH <n>writes
statusLine.refreshInterval, in whole seconds (at least 1, no unit).- Without it, Claude Code (verified on 2.1.283) does not re-render an idle
status line, so anything that rides on renders stops. Kommander's
database-lease heartbeat is one example, and statusmux another. SET STATUSLINE REFRESH <n>changes only the interval, and is refused
when there is no status line.UNSET STATUSLINE REFRESHremoves only the
interval.- A
SET STATUSLINE '<command>'without REFRESH keeps an existing
interval, as it keepspadding, andSHOW CREATEround-trips it. SHOW --jsonandSELECTgainstatusline_refresh.SHOWand
EXPLAINprint(refreshes every <n> s).
- Without it, Claude Code (verified on 2.1.283) does not re-render an idle
CREATE PLAYBOOK … NO PILOT PROFILE(hidden:create --no-pilot-profile) writes CLAUDE.md without the~/.pilot-profile/
imports.- Claude Code sends whatever CLAUDE.md imports with every request. For a
playbook routed to another provider (a local router, EVREN, GLM,
DeepSeek), the profile would go with them. - It applies at create time only, and is refused with
FROMorLINK.
- Claude Code sends whatever CLAUDE.md imports with every request. For a
- A warning when the profile would leave Anthropic. Take a statement
that gives a playbook importing~/.pilot-profile/a non-Anthropic
ANTHROPIC_BASE_URL: its own block, an env set it uses,DEFAULTS, or its
creation under them. It prints one line naming the playbook and the host.- In
APPLY --json, it has the stable code
pilot_profile_third_party_endpoint. - It fires only on the statement that makes this so, and never refuses.
localhostcounts, since a local proxy forwards elsewhere.- The routed playbooks in examples 02, 04 and 13, the README and the
tutorial now useNO PILOT PROFILE.
- In
ISOLATED LOGINis a playbook that shares no login with~/.claude,
without a sandbox:CREATE PLAYBOOK … ISOLATED LOGIN,create --isolated-login,ALTER PLAYBOOK … SET | UNSET ISOLATED LOGIN.- It writes
isolate_auth = true.SETremoves the link to the shared
login at once. UNSETis refused while the playbook holds a login of its own, since a
shared launch would copy that login over the machine's and switch your
account. It is also refused on a sandboxed playbook.SHOW,EXPLAIN,SHOW --json/SELECTisolated_login, and
SHOW CREATEread it.
- It writes
Nothing breaks. Every v3.22.1 statement and file keeps its meaning.
statusline in SHOW/SELECT is unchanged, and a playbook created without the
new clauses gets exactly what it got before.
Docs:
- reference sections for each, plus the warning-codes list and the migration
table; - examples 10 (REFRESH), 15 (a third-party route) and 16 (an isolated login),
and the Kommander recipe (README, example 08) now setsREFRESH 10; - an AGENTS.md safety rule: a playbook for a non-Anthropic route, or a
throwaway, is createdNO PILOT PROFILE ISOLATED LOGIN; - the configure-an-agent, agent, environment and authentication guides, and
the first-playbook tutorial.
Verification
-
CI is green on ubuntu (Go 1.21) and macOS (Go 1.26) for each PR (#116,
#119, #120). That covers the examples job (16 examples, with coverage.sh
requiring an executed example for every clause). -
Arena: each PR extended cli-grammar with its own assertion, confirmed by
root for the pilot:statusline-refresh-ok,no-pilot-profile-okand
isolated-login-ok. There are now 32 commands plus 1 file. Targeted
bench runs:Phase 2 runs on the tag commit.
-
Codex was at quota, so Antigravity (Gemini 3.8 Flash) was the review of
record on each PR. Its findings on ISOLATED LOGIN (a sandboxed playbook's
read, and a dry-run rename) were fixed with regression tests before merge. -
Every test and check ran on throwaway playbooks and made-up credential
stores. None of the pilot's playbooks or env sets were touched.
Release gate: arena phase 2 run 36317554178 on de86419 (success).
v3.22.1 — security: installing a playbook never carries a login
Security fix: a playbook source never carries a login
Installing a playbook could replace your Claude login with someone else's.
Before v3.22.1, suppose a playbook source (a directory, or a git repository)
contained a .credentials.json, Claude Code's login store. cpb copied it into
the install, and the first credential sync then copied that file over
~/.claude/.credentials.json, because it was newer. From then on, every
playbook that shares the machine login ran as the source's account. A
.claude.json shipped in a source could seed an account identity the same way
(oauthAccount, userID, cached feature flags).
Update: claude-playbook update fetches it, or install the binary as usual.
-
CREATE PLAYBOOK … FROMandinstall: a source's
.credentials.json(a file or a link) and the account state in its
.claude.jsonstay out of the install, at its root and in its config
subdirectory. Each prints one line on stderr naming the source and the
keys, never a value:Warning: ignored <source>'s .credentials.json: a playbook source never carries a loginThe rest of the source installs as before, and the source is not touched.
-
CREATE PLAYBOOK … LINKandlinkwork in place, so nothing is
deleted. A.credentials.jsonthere is renamed to
.credentials.json.cpb-ignored-<stamp>, and.claude.jsonis backed up
before the account keys leave it. A directory withisolate_auth = true
keeps both, since its login is its own. -
updatewas not affected: it already kept the install's own
.credentials.jsonand.claude.json.
Were you affected? Run cpb auth status. It prints store kinds and modes,
never values. A shared-login playbook whose STORE is file holds a login of
its own. Also check that claude auth status in ~/.claude shows your
account.
Still open, and documented in
docs/known-issues/shared-launch-copies-own-login-over-machine-login.md:
a shared launch still prefers a playbook's own newer login over the machine's
in other cases. One example is a login made during a one-off
CLAUDE_PLAYBOOKS_ISOLATE_AUTH=true launch. The planned fix, a same-account
check in the sync, needs a design pass. v3.23.0's ISOLATED LOGIN refuses to
turn isolation off while a playbook holds its own login.
Verification
-
Tests reproduce the attack through:
- a directory source and a
file://git source; - a shipped credentials link, and a shipped
.claude.jsonthat links out
of the source; - LINK, next to an isolated LINK (with and without a config
subdirectory); - a linked directory whose
.credentials.jsonlinks to another account's
store, followed by a real launch.
Each asserts the machine store is byte-for-byte unchanged. Run against the
v3.22.0 code, the install, LINK and linked-state tests fail. - a directory source and a
-
Arena: cli-grammar gains
install-never-carries-login-ok, which runs on the
real binary. -
Review: Antigravity on Gemini 3.8 Flash. Codex was at quota.
-
Everything ran in throwaway homes with made-up stores. No real credential
was read or used.
Release gate: arena phase 2 run 36313160822 on 9960d62 (success).
v3.22.0 — the /model picker, plans a program can read, readable SELECT, DESCRIBE
Highlights
Plans a program can read, and the /model picker.
cpb APPLY agent.cpb TO reviewer --dry-run --json
ALTER PLAYBOOK router-agent
ADD MODEL 'glm-5.3' LABEL 'GLM 5.3' DESCRIPTION 'via the router'
ADD MODEL 'glm-5.3-flash' LABEL 'GLM 5.3 Flash' BEHAVES AS 'claude-sonnet-5'
SET MODEL PICKER ONLY;
-
APPLY … --dry-run --json: the plan as one JSON object,schema1, on
stdout every time, refusals included. It gives, per statement:- the resolved file and line, and the target (playbook, dir, env set or
DEFAULTS); - the verdict (created, changed, unchanged, dropped or refused, with a
reason); - the actions a real run would take:
claude pluginandclaude mcp
commands with their exact argv, skills, fetches, backups, writes, and
deletes with their size on disk. Each carries anetworkflag.
References appear as references and secret values never appear. Warning
codes are stable. Exit codes: 0 planned, 1 refused by the files, 2 usage.
The schema was reviewed with cockpit, its first consumer. - the resolved file and line, and the target (playbook, dir, env set or
-
A dry run writes nothing, not even the registry lock file.
-
The model picker:
ADD MODEL '<id>' [LABEL] [DESCRIPTION] [BEHAVES AS],DROP MODEL,
SET MODEL PICKER ONLY | APPENDandUNSET MODEL PICKERwrite
settings.jsonmodelPickeronly. Rows are keyed by model id, and rows
and keys cpb did not write are kept.- SHOW, EXPLAIN, SHOW CREATE and SELECT read it back, and it is valid on a
plain config directory. - Claude Code reads it from 2.1.242, and
behavesAsfrom 2.1.257.
-
A clear refusal for an old claude: the plugin clauses need Claude Code
2.1.268 or newer, the first withclaude plugin install --json. An older
one (nixpkgs has carried 2.1.245) is refused in one line naming both
versions, before any command changes anything. -
SELECTis readable on a terminal (the pilot's report). With no
FORMATin the query, cpb asksclickhouse localfor JSON and renders the
result the way the built-in form does:- a table up to 6 columns, one block per row beyond, so
SELECT *does
not wrap; - objects and arrays of objects as JSON,
/unescaped; NULLand empty values as-.
A pipe still gets TSV, and a
FORMATin the query always wins. - a table up to 6 columns, one block per row beyond, so
-
DESCRIBE [TABLE] <table>(orDESC) lists aSELECTtable's columns
and types,--jsontoo. -
Fixed before release (found by the Antigravity review on Gemini 3.8
Flash):- a parse error's
error.fileinAPPLY --jsonis now the resolved
path; SHOW CREATEno longer writes a model picker row that would not
re-parse (abehavesAswith a space, a multi-line label); it becomes a
comment instead;- dead code removed.
- a parse error's
Nothing breaks. Every v3.21.0 statement and file keeps its meaning, and
the human output of APPLY is unchanged.
Docs:
- reference sections for each (APPLY --json, the model picker, the Claude
Code minimum, what a terminal and a pipe get from SELECT, DESCRIBE); - example 14 (the model picker), a
--jsonstep in example 12, and DESCRIBE
in example 13; - the configure-an-agent and agent guides, and a Nix note in installation;
- AGENTS.md prerequisites carry the Claude Code minimums.
Verification
- CI is green on ubuntu (Go 1.21) and macOS (Go 1.26) for every PR in the
series (#109, #110, #111, #114, #115, the docs PR). That covers the examples job (14
examples, and coverage.sh requiring an executed example for every clause)
and a real clickhouse-local job. - Arena: every feature PR extended cli-grammar with its own assertions,
confirmed by root for the pilot:apply-json-ok,model-picker-ok,
claude-version-okanddescribe-ok, now 29 assertions. Each PR had a targeted bench run: 36299194911
(26/26), 36300235030 (27/27), 36306634013 (#111, 28/28), and #115's on
its final head. The judged goal pilot
cpb-goal-grammarpasses 3/3 in phase 2 and runs nightly. Phase 2 runs on
the tag commit (the release gate). - Codex was at quota, so Antigravity was the review of record on each PR,
switched to Gemini 3.8 Flash on the pilot's call. A Flash re-review of the
earlier PRs found four real issues that Pro had missed; all four were
fixed before the tag (#114, #112). - Every test and check ran on throwaway playbooks. None of the pilot's
playbooks or env sets were touched.
v3.21.0 — a playbook file fully defines an agent
Highlights
A playbook file now describes a whole Claude Code agent, and writes it anywhere.
-- kommander.cpb: a recipe (it names no playbook)
INCLUDE 'bare.cpb';
ALTER PLAYBOOK
ADD MARKETPLACE kommander FROM '~/path/to/kommander-playbook'
ADD PLUGIN kommander@kommander
SET AGENT 'kommander'
ALLOW TOOL 'Bash(kommander-helper *)'
SET STATUSLINE 'bash ~/path/to/kommander-playbook/hooks/statusline.sh';
cpb APPLY kommander.cpb TO kommander-agent --dry-run
cpb APPLY kommander.cpb TO kommander-agent
- MCP servers:
ADD / DROP MCP SERVER <name>, stdio (COMMAND … ARGS …)
or remote (URL …,TRANSPORT SSE), withENVandHEADER. cpb runs
Claude Code's ownclaude mcp add-json / remove --scope userfor the
playbook. A credential takes a reference only: Claude's config holds a
${CPB_MCP_…}placeholder and the secret helper resolves it at launch. - Tools, status line, model:
ALLOW / DENY / UNSET TOOL '<rule>',
SET / UNSET STATUSLINE,SET / UNSET MODEL, written into the playbook's
settings.json, keeping every key cpb did not write. - Skills:
ADD / DROP SKILL <name> FROM <source>. A directory is linked,
so edits reach the next session; a git source (github:,https://,
git@,file://, withBRANCHandSUBDIR) is cloned and copied, and
cpb updaterefreshes it. cpb records what it adds and removes only that. - Recipes and targets:
ALTER PLAYBOOKwith no name is a recipe. Its
target comes fromAPPLY … TO <playbook>(created bare if missing),
TO '<dir>'(a plain Claude Code config directory such as~/.claude,
backed up once per run and confirmed first), or aUSE PLAYBOOKline. - SELECT:
cpb "SELECT name, envs FROM PLAYBOOKS"overPLAYBOOKS,
ENVS,VARSandDEFAULTS. Columns only is built in; anything else runs
inclickhouse localover exactly the redactedSHOW … --jsonrows.
EXPLAIN SELECTshows which engine runs a query, and the exact command. - Ordering: plugin, MCP and skill clauses run in the order written, the
first failure stops the statement, and running it again finishes it.
Nothing breaks. Every v3.20.0 statement and file keeps its meaning. The
manifest gains [mcp.<name>] and [skills.<name>] records; cpb update
carries them and restores recorded skills after the overlay.
Docs: examples 09-13 (MCP servers, tools/status line/model, skills,
recipes and targets, SELECT); example 08 rewritten as recipes with the
Kommander permission and status line; a "Configure an agent" guide; the
tutorial, reference, SQL and agent guides current. examples/coverage.sh
fails CI when a grammar clause has no reference entry or example, and
AGENTS.md carries the release-docs rule.
Verification
- CI green on ubuntu (Go 1.21) and macOS (Go 1.26) for every PR in the series
(#96-#102), including the examples job (13 examples) and a real
clickhouse-local job for SELECT. - Each PR went through Codex once, with an Antigravity second look on the fix
rounds (Antigravity was the review of record for #104 and #106, with Codex
at quota). - Arena: the cli-grammar suite covers every new clause (25 assertions on the
real binary: MCP servers with a credential by reference, tools / status
line / model, skills, recipes and TO, TO a plain directory, SELECT, a git
marketplace's #ref); targeted bench run 36272893243 passed 25/25. Then the
full phase-2 regression on the tag commit (run 36273133296, release gate). - Fixed before release: a git marketplace source with #ref was refused on
re-apply (#104, found by the kommander agent).
v3.20.0 — playbook files: a SQL-like grammar for Claude Code setups
Highlights
A statement grammar for cpb, and playbook files. State is changed and read
with cpb <VERB> <OBJECT> <name> <clause> ..., read and written like SQL DDL.
-- chaos.cpb
INCLUDE 'kommander.cpb';
ALTER PLAYBOOK kommander-agent
ADD MARKETPLACE chaos-stub FROM './chaos-stub'
ADD PLUGIN chaos@chaos-stub;
cpb APPLY chaos.cpb --dry-run
cpb APPLY chaos.cpb
- Statements:
CREATE / ALTER / DROPonPLAYBOOK,ENV(named env sets)
andDEFAULTS(an ordered list of env sets under every playbook);SHOWand
EXPLAINwith a stable--json. A barecpb SHOWlists playbooks. - Env sets in order:
USE ENV a b,ADD ENV x FIRST|LAST|BEFORE y|AFTER y,
DROP ENV; a later set wins, a playbook's ownSET VARwins over all;
BLOCK VARremoves a variable at launch. - Secrets by reference:
SET K FROM '<ref>'through a secret helper you
configure (ALTER DEFAULTS SET SECRET HELPER, orCPB_SECRET_HELPER); checked
before writing, resolved only at launch. A credential-looking literal is
refused unlessAS PLAINTEXT; no output ever prints one. - Playbook files:
SHOW CREATE ALL > playbook.cpbwrites a machine as
statements that are safe to repeat;APPLYvalidates everything before
writing, runs in order, stops at the first failure, and a dry run judges each
statement against what the earlier ones would have written.INCLUDEstacks
files; a file reached twice runs once; a cycle is refused. - Plugins and the agent:
ADD / DROP MARKETPLACE,ADD / DROP PLUGINrun
Claude Code's ownclaude plugincommands for that playbook only (state read
first, no-ops run nothing);SET AGENTpins the main-thread agent. A
marketplace-declared command is never accepted for you. - Lifecycle:
CREATE PLAYBOOK [FROM|LINK] [ALIAS|NO ALIAS] [SANDBOX],
ALTER PLAYBOOK … RENAME TO / ALIAS / NO ALIAS,DROP PLAYBOOK … --yes.
cpb install <url>stays as the shortcut. - The goal, proven:
examples/08-kommander-agentbuilds Kommander as an
agent from three stacked files (bare -> kommander -> a layer on top).
Nothing breaks. The pre-grammar commands (env, env-profile,
create <name>, link, delete, rename, alias, dealias, list, info)
keep working on their own code paths, hidden from help; on a terminal each
prints one stderr line naming its statement. Scripts should move to
SHOW … --json before they are removed in v4.0.0. Formats: [env.refs] / [refs]
tables are new, and .env-profiles/.default holds one name per line.
Docs: a README built around the grammar, two tutorials, examples 01-08 (all
applied in CI), guides rewritten grammar-first, a "Query with SQL" guide, and
the grammar reference (docs/reference/cli-grammar.md).
Verification
- CI green on ubuntu (Go 1.21) and macOS (Go 1.26) for every PR in the series.
- Arena: cli-grammar suite (17 assertions, on the real binary and launch path),
plus the full phase-2 regression on the tag commit (release gate). - Proof run 2026-09-26 on macminim:
cpb APPLY chaos.cpbbuilt
kommander-agent; a launch answered as Kommander and followed the stacked
layer's instruction; applying again changed nothing.
Not in this release (planned for v3.21.0)
MCP servers, permissions (ALLOW TOOL), status line and model, standalone skills,
APPLY … TO <playbook|dir> with name-less ALTER PLAYBOOK and USE PLAYBOOK, and
SELECT (a built-in column subset, full SQL handed to clickhouse local when
installed). Until then, cpb SHOW … --json | ch local --input-format JSONEachRow
works today (docs/guides/query-with-sql.md). TAB completion of statements and
system-prompt files (P11) are not scheduled.