Skip to content

Releases: ramazanpolat/claude-playbooks

v3.27.0 — experimental OpenShell sandbox backend; github:owner/repo#ref pins

Choose a tag to compare

@github-actions github-actions released this 01 Oct 08:48
ab36472

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 first

What 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 (:ro read-only);
    • the session running as your user;
    • Claude Code's hosts allowed for the claude binary only.
  • 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).
  • 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;
    • openshell is 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.

    --clone and share_skills are sbx'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.0 to claude plugin marketplace add, the
    form Claude Code itself writes.
  • # and @ spell one source, and applying again changes nothing.
  • SHOW CREATE writes it back, and APPLY of that output changes nothing.
  • A commit cannot be pinned. Claude Code clones a marketplace by branch
    or tag only, so a github: 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/repo without a ref is unchanged.

Internal

  • The drift monitor watches the stable line (#153). The nightly runs the
    full arena on main and on the newest release/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 whole sbx call log
    is pinned by a golden test.

Changes a result

Nothing changes a result. Everything is additive:

  • a backend value, openshell, with its [sandbox] backend key;
  • 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),
    the upgrade job included.
  • Arena:
    • cli-grammar gains github-ref-ok, which fails against main before #154;
      targeted run 36829764506 on #154 passed 46/46;
    • the drift monitor was proven after #153, on main (164/164) and
      release/v3.24 (155/155), and a tag dispatch now fails loudly;
    • the full phase 2 on the tag commit is listed above.
  • OpenShell e2e on the Linux test machine: 27/27 (tests/openshell-e2e.sh).
  • Review:
    • Codex: #151 was clean. #152 had three inline findings and #153 one
      (the run lookup that could judge an older run), all fixed before merge.
      #154 had no Codex review: its usage limit was reached that day.
    • Antigravity (Gemini 3.8 Flash):
      • #152: two rounds, 10 findings and then 1, each fixed or answered;
      • #153: one round;
      • #154: the review of record, with two clean passes.
  • Upgrade: from v3.26.0, via the CI upgrade job 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

Choose a tag to compare

@github-actions github-actions released this 30 Sep 11:35
38ffb55

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_VERSION is 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 … --json key 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), the upgrade job included.

  • Arena: cli-grammar gains select-order-ok and
    sandbox-secret-refuse-ok (a stub sbx). 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:

    • TestSelectJSONKeyOrder covers every table's columns reversed;
    • .github/scripts/npx-shim_test.sh has 33 checks with a stub curl, on
      both OSes;
    • the sandbox refusal test uses a canary key that must appear in no
      message, and no sbx call but its registration;
    • TestRunSandboxRefusalRestoresSharedLogin covers a failed registration
      and a failed create.

    Each test fails against the code before its fix.

  • Review:

    • Codex (once per PR):

      • #145 found the release check's warning promising a fallback across a
        major version;
      • #146 found the routed example missing ISOLATED LOGIN, and the
        sandbox's key promise unqualified;
      • #149 found the shared-login link left dangling on a refusal.

      All were fixed before merge; #144 and #147 were clean.

    • 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 upgrade job on the tag commit
    (CI run 36703676380, green on Ubuntu and macOS).

  • Everything ran on throwaway playbooks, a throwaway HOME, stub sbx and
    curl, and made-up keys.

v3.25.0 — cpb tui, sessions and RESUME, status line history and panels

Choose a tag to compare

@github-actions github-actions released this 29 Sep 12:28
14b7287

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:
    • c shows SHOW CREATE (without secrets);
    • y copies the statement behind the selection;
    • e exports the selection as <name>.cpb, a recipe cpb APPLY
      re-applies. It asks before replacing a file, and writes atomically.
  • Resume: enter on a recent session that is not running resumes it
    through its playbook, as cpb RESUME does, 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 cpb in a terminal ends with the hint Browse and manage them: cpb tui.

No cost for everything else.

  • Built on: bubbletea v2 (charm.land/bubbletea/v2 v2.0.10, bubbles
    v2.1.1, lipgloss v2.0.6), pinned.
  • Startup: cpb SHOW PLAYBOOKS starts 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> and DROP 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) and OBSERVE (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 STATUSLINE turns 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
      say AS PLAINTEXT. SHOW CREATE never prints one.
    • Reads. SHOW PLAYBOOK --json lists the panels, with enabled
      plugins' panels read-only, as does SELECT … FROM PANELS. EXPLAIN
      says when the status line is not a host, so the panels would not render.
  • SET STATUSLINE PREVIOUS puts back the status line a statement
    replaced last, padding and refreshInterval included. Twice toggles
    back.
    • cpb keeps a short history (10 per directory) in its own state,
      .state/statusline-history.json.
    • SHOW --json and SELECT show the history, as statusline_history.

Sessions: see them and resume them in the right playbook. (#132)

  • cpb sessions (short for SHOW 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 RESUME resumes 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,
    and RESUME --list lists 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 SESSIONS shows each session's
    terminal, as tty (pts/3, ttys012), or null for a background
    session. It comes from /proc on Linux and ps on 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, and SELECT … FROM PLAYBOOKS has 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
    --json fields 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.26 build and the release build all use 1.26.
  • The Nix flake's nixpkgs already built with Go 1.26.
  • Release binaries and npx are 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's release/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 command sessions;
  • the tables PANELS and SESSIONS;
  • --json fields: panels and statusline_history; pilot_profile, the
    last field of SHOW PLAYBOOK; the SHOW SESSIONS --json and RESUME --list --json shapes, with tty as the last session field;
  • SELECT columns: SESSIONS.tty and PLAYBOOKS.pilot_profile, each last.
    On the clickhouse-local path, SELECT * lists pilot_profile after the
    computed version_tuple, so every earlier position holds;
  • the APPLY --json delete action value panel;
  • cpb state files: .state/statusline-history.json, and the manifests
    under statusline.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.md and 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:

    • green for #130, #131, #132, #134 and #135, on Ubuntu (Go 1.21, before
      #137) and macOS (Go 1.26);
    • green for #137 on both, with Go 1.26;
    • the upgrade job included.
  • Arena: cli-grammar gains statusline-history-ok and panels-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.26 bench 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 STATUSLINE bypass 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 ClickHouse SELECT * column order. All were
      fixed before merge.
  • Upgrade: from v3.24.0, via the CI upgrade job on the tag commit
    (CI run 36562657793, green on Ubuntu and macOS).

  • E...

Read more

v3.24.0 — the first stable release

Choose a tag to compare

@github-actions github-actions released this 29 Sep 11:04
debb155

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 --json field 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 or SELECT column. 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 the settings.json keys cpb writes.
  • The --json shapes: SHOW, EXPLAIN, APPLY --dry-run --json
    (schema 1), SELECT / DESCRIBE, and auth status --json.
  • The codes: the four APPLY warning 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 to SHOW … --json or EXPLAIN … --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 upgrade CI 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 --json and
      auth status --json;
    • no change on a re-apply;
    • every playbook launching and dropping;
    • a made-up machine login never touched.
  • 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: the upgrade job 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.24 for 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), a SET STATUSLINE '<another command>'
    (a recipe re-applied, say) leaves it in place and warns:
    statusline_held_by_host.

    • REFRESH still applies.
    • UNSET STATUSLINE is 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.json could 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 new upgrade job.
  • 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

Choose a tag to compare

@github-actions github-actions released this 27 Sep 14:14
7df50d7

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 accountUuid in the playbook's
    .claude.json must 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.
  • 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

Choose a tag to compare

@github-actions github-actions released this 27 Sep 12:53
de86419

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 REFRESH removes only the
      interval.
    • A SET STATUSLINE '<command>' without REFRESH keeps an existing
      interval, as it keeps padding, and SHOW CREATE round-trips it.
    • SHOW --json and SELECT gain statusline_refresh. SHOW and
      EXPLAIN print (refreshes every <n> s).
  • 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 FROM or LINK.
  • 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.
    • localhost counts, since a local proxy forwards elsewhere.
    • The routed playbooks in examples 02, 04 and 13, the README and the
      tutorial now use NO PILOT PROFILE.
  • ISOLATED LOGIN is 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. SET removes the link to the shared
      login at once.
    • UNSET is 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 / SELECT isolated_login, and
      SHOW CREATE read it.

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 sets REFRESH 10;
  • an AGENTS.md safety rule: a playbook for a non-Anthropic route, or a
    throwaway, is created NO 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-ok and
    isolated-login-ok. There are now 32 commands plus 1 file. Targeted
    bench runs:

    • 36315913718 (#116, 31/31);
    • 36316380455 (#119, 32/32);
    • 36316983400 (#120, 33/33).

    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

Choose a tag to compare

@github-actions github-actions released this 27 Sep 11:26
9960d62

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 … FROM and install: a source's
    .credentials.json (a file or a link) and the account state in its
    .claude.json stay 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 login
    

    The rest of the source installs as before, and the source is not touched.

  • CREATE PLAYBOOK … LINK and link work in place, so nothing is
    deleted. A .credentials.json there is renamed to
    .credentials.json.cpb-ignored-<stamp>, and .claude.json is backed up
    before the account keys leave it. A directory with isolate_auth = true
    keeps both, since its login is its own.

  • update was not affected: it already kept the install's own
    .credentials.json and .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.json that links out
      of the source;
    • LINK, next to an isolated LINK (with and without a config
      subdirectory);
    • a linked directory whose .credentials.json links 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.

  • 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

Choose a tag to compare

@github-actions github-actions released this 27 Sep 09:43
95dcd76

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, schema 1, 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 plugin and claude mcp
      commands with their exact argv, skills, fetches, backups, writes, and
      deletes with their size on disk. Each carries a network flag.

    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.

  • 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 | APPEND and UNSET MODEL PICKER write
      settings.json modelPicker only. 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 behavesAs from 2.1.257.
  • A clear refusal for an old claude: the plugin clauses need Claude Code
    2.1.268 or newer, the first with claude 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.

  • SELECT is readable on a terminal (the pilot's report). With no
    FORMAT in the query, cpb asks clickhouse local for 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;
    • NULL and empty values as -.

    A pipe still gets TSV, and a FORMAT in the query always wins.

  • DESCRIBE [TABLE] <table> (or DESC) lists a SELECT table's columns
    and types, --json too.

  • Fixed before release (found by the Antigravity review on Gemini 3.8
    Flash):

    • a parse error's error.file in APPLY --json is now the resolved
      path;
    • SHOW CREATE no longer writes a model picker row that would not
      re-parse (a behavesAs with a space, a multi-line label); it becomes a
      comment instead;
    • dead code removed.

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 --json step 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-ok and describe-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-grammar passes 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

Choose a tag to compare

@github-actions github-actions released this 26 Sep 22:10
3787195

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), with ENV and HEADER. cpb runs
    Claude Code's own claude mcp add-json / remove --scope user for 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://, with BRANCH and SUBDIR) is cloned and copied, and
    cpb update refreshes it. cpb records what it adds and removes only that.
  • Recipes and targets: ALTER PLAYBOOK with no name is a recipe. Its
    target comes from APPLY … 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 a USE PLAYBOOK line.
  • SELECT: cpb "SELECT name, envs FROM PLAYBOOKS" over PLAYBOOKS,
    ENVS, VARS and DEFAULTS. Columns only is built in; anything else runs
    in clickhouse local over exactly the redacted SHOW … --json rows.
    EXPLAIN SELECT shows 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

Choose a tag to compare

@github-actions github-actions released this 26 Sep 19:01
3a42fd2

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 / DROP on PLAYBOOK, ENV (named env sets)
    and DEFAULTS (an ordered list of env sets under every playbook); SHOW and
    EXPLAIN with a stable --json. A bare cpb SHOW lists 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 own SET VAR wins over all;
    BLOCK VAR removes a variable at launch.
  • Secrets by reference: SET K FROM '<ref>' through a secret helper you
    configure (ALTER DEFAULTS SET SECRET HELPER, or CPB_SECRET_HELPER); checked
    before writing, resolved only at launch. A credential-looking literal is
    refused unless AS PLAINTEXT; no output ever prints one.
  • Playbook files: SHOW CREATE ALL > playbook.cpb writes a machine as
    statements that are safe to repeat; APPLY validates 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. INCLUDE stacks
    files; a file reached twice runs once; a cycle is refused.
  • Plugins and the agent: ADD / DROP MARKETPLACE, ADD / DROP PLUGIN run
    Claude Code's own claude plugin commands for that playbook only (state read
    first, no-ops run nothing); SET AGENT pins 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-agent builds 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.cpb built
    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.