Changelog
What's new in cctabs. Source of truth lives in CHANGELOG.md on GitHub.
Install or update:
sh
# Claude Code plugin (recommended)
/plugin install cctabs@generativereality
# Or as a standalone CLI
npm i -g @generativereality/cctabsUnreleased
- Tabs can be coloured.
--coloronnew,resumeandforksets the Tabby tab colour, andcctabs color <tab> <colour>changes one that already exists — including a tabresumereused rather than created, which a create-time-only flag would have missed. Values are Tabby's own palette (blue,green,orange,purple,red,yellow),noneto clear, or any 3-, 6- or 8-digit hex. The motivation is fleets that span several Claude accounts:[backends.<name>] colorcolours one account's tabs, so which account a tab belongs to is visible in the tab bar rather than something you have to remember.[defaults] colorcolours everything; precedence is--color→ the preset'scolor→[defaults]. - Named colours resolve to Tabby's exact hex values, not merely similar ones. Tabby's right-click → Color menu renders its radio state by comparing
tab.coloragainst the literals in its ownTAB_COLORS, so mapping "blue" to any other blue would colour the tab correctly and still leave that menu showing no selection — a cctabs-set colour that looks subtly unlike a hand-set one. The plugin setsBaseTabComponent.colordirectly, which is exactly what the menu's own click handler does, so the two are indistinguishable and Tabby's tab recovery persists both alike. - An unrecognised colour fails instead of doing nothing. Tabby binds the value straight into a CSS
background-color, so a colour it doesn't understand simply fails to render — silently, and identically to a colour that never arrived. Validation happens before the terminal is touched, so a typo costs nothing: no tab, and (onnew --worktree) no worktree left behind. - Colour support is a plugin capability, probed rather than assumed. A user's installed plugin is routinely older than the CLI talking to it, and an older
tabby-cctabsdrops an unknowncolorfield onPOST /api/tabs/newwithout complaint — which is indistinguishable from a colour that was accepted and didn't render.new/resume/forktherefore ask first and, when the answer is no, warn once and open the tab uncoloured: losing the spawn and whatever was going to run in it would be much the worse trade for a cosmetic field.cctabs colorexits non-zero instead, because there colouring was the whole request. - A tab's colour survives a reboot. Tabby does persist a tab colour across its own restart, but that isn't enough:
cctabs restorerecreates a dead tab — closes it and spawns a new one — and a fresh tab starts uncoloured, so a restored fleet came back colourless. Colour now travels the same routepermission_modealready does.cctabs sessions --jsonrecordscolor,restore --manifesthands it back, and a scan-mode restore reads the colour off the tab it is about to replace. A recordednullis honoured as "deliberately uncoloured" rather than treated as missing. - …and a rule like "the enterprise account's tabs are blue" holds even with nothing recorded. When an entry carries no colour — a manifest predating the field, or a tab Tabby never recovered — the colour falls back to what the config implies for that entry's backend:
[backends.<name>] color, else[defaults] color. The backend is already inferred from the Claude config dir the session was found in, so the colour follows the account for free. - Fix: a
[backends.<name>]section replaced a builtin preset instead of overlaying it. Pre-existing, but this release is what makes it easy to hit, because adding acolorto a preset is now an obvious thing to write:[backends.kimi]\ncolor = "red"silently discarded kimi'sANTHROPIC_BASE_URL, auth token and model, leaving a preset that looked configured and quietly talked to the default Anthropic API instead. Sections now merge over the builtin of the same name —envkey-by-key, and unsetmodel/description/colorfalling through — so one key can be overridden without restating the rest. - Plugin:
tab-colorcapability. Tabs report theircoloronGET /api/tabs,POST /api/tabs/newaccepts an optionalcolor(applied as part of the create, so a tab never renders uncoloured first), andPUT /api/tabs/:uuid/colorsets or clears it on a live tab. An invalid colour is a 400 on thePUTbut only a logged warning on the create — failing a spawn over a cosmetic field is the worse outcome. Needs a rebuilttabby-cctabs; without it the CLI degrades as above. cctabs profile-copymoves a session between Claude accounts.CLAUDE_CONFIG_DIRisolates each profile's transcripts, so a session started under one account is invisible to anything running under another — and moving one across by hand means knowing five separate ways to get it wrong.cctabs profile-copy <tab|session-id> --to <preset>copies the transcript and its sidecar into the target profile, names the copy distinctly, and opens it in a tab under that account.--tonames a backend preset rather than a bare config dir because on macOS the Keychain holds one Claude Code login per OS user and does not scope it byCLAUDE_CONFIG_DIR: running under another profile depends on that profile'sCLAUDE_CODE_OAUTH_TOKEN, which presets already carry. A bare path is accepted with a warning.- The sidecar travels with the session — and
export/importwere silently dropping it. A session's subagent transcripts and tool results live in a<session-id>/directory beside the.jsonl; one real session had 357 files in it.cctabs exportcopied only the transcript, so an imported session resumed having forgotten every subagent, with nothing anywhere saying so. Sidecars are now bundled astabs/<name>/sidecar/and restored on import. Archives written before this simply have none, and import handles both, so no format bump. exportcouldn't see a second Claude account at all. It resolved sessions across every config dir but then built the transcript path as~/.claude/projects/<slug>/…unconditionally, so exporting anything belonging to a preset-owned profile failed with "session file missing". The transcript is now read from the config dir it was actually found in, and the owning preset is recorded in the manifest asbackendsoimportputs the session back under the same account. Importing into the wrong config dir doesn't fail loudly —claude --resumejust can't find the id and opens a fresh conversation — so this was a data-loss-shaped bug rather than an error message.- A move refuses while the source is still running. A
mvwithin one filesystem is a rename: the inode is unchanged, so a liveclaude's open descriptor follows the file, and the old tab and the new one then append to the same transcript, interleaving two conversations into one unusable file. The default is therefore a copy, which diverges cleanly the way--fork-sessiondoes.--movechecks the source tab's pid and refuses, pointing at--close-source. - Closing a tab is not the same event as its process exiting, and the gap loses data. A closing Claude Code writes a metadata trailer —
custom-title,agent-name,mode,permission-mode,pr-link, and no conversation content — back to its transcript path aftercctabs closehas already reported success. Move the transcript in that window and the trailer recreates the file at the old path, with a fresh mtime and acustomTitle. Since name resolution matches on customTitle and prefers the newest mtime, that stub outranks the relocated original and quietly shadows it on the nextrestore.--close-sourcenow waits for the pid to actually disappear (not just the tab), then re-checks the old path and sweeps the trailer — and only ever removes a file it can prove is metadata-only and belongs to that session id. - A session whose directory no longer exists is filed under the repo root instead.
claude --resume <id>fails withNo conversation found with session IDwhen the transcript sits under the project slug of a deleted directory — a removed worktree, usually — even though the file is right there. The target slug is chosen as the last recorded cwd that still exists, scanning the transcript backwards, falling back to the repo root. Last, not first: a session can start in one worktree,cdelsewhere, and end in another, and the transcript is filed under the one it started in. Picking the transcript's own slug would reproduce the dead-path failure inside the target profile. - After a relocating move, the stale project dir is archived out of
projects/entirely. A leftover worktree-named project directory is itself read as evidence that the worktree still exists, which sends a laterrestoreinto a deleted path. Renaming it in place does not help — matching is on thecustomTitleinside the transcripts, not on the directory name — so it is moved to<config-dir>/cctabs-archived-projects/<slug>-<timestamp>instead. Only ever when the dir has no transcripts left and the directory its slug names is genuinely gone. cctabs fork's private worktree-path handling is now the sharedrepoRootOf()insrc/core/worktree.ts, whichprofile-copyneeds for the same reason: it has to keep working after the worktree directory has been deleted, which is exactly when it's called.
0.5.2 — 2026-08-15
restorereported success for tabs that never opened a session. Claude has two startup dialogs that block a tab until answered — the folder-trust check ("Is this a project you created or one you trust?") and, new, "Set up auto mode for your environment?". Both render after the process starts and before the conversation loads, so a tab sitting on one is indistinguishable from a healthy launch: the process is alive,restoreprints✔ spawned, and nothing ever resumes. On a real 65-tab restore this stranded 10 tabs — 8 on trust, 2 on auto-mode — and the only symptom that surfaced anywhere was an unreadable permission mode, because a blocked tab never paints a footer. Trust was handled, but only on thecctabs newpath; restore and resume went throughconfirmResumePicker, which knew about the resume picker and the mobile-app overlay and neither dialog. Both are now cleared on every path, by one sharedclearStartupDialogs.- The two dialogs need opposite answers, and the difference matters. Enter on the trust dialog takes its default, "Yes, I trust this folder". Enter on the auto-mode dialog takes its default — "Set it up" — which starts an interactive pass over your repo and recent sessions. That is not something a restore should trigger unattended across a fleet, so the auto-mode dialog is answered with ↓ once then Enter, selecting "Not now". Option 3 ("Don't show again") is deliberately never reachable: permanently suppressing a prompt is the user's call, the same reasoning that already governs the resume picker's option 3.
- Trust is auto-confirmed only where the user already trusted the folder.
restoreandresumeconfirm the dialog when the directory already holds transcripts under some config dir — evidence that Claude has worked there before, so the decision was the user's, earlier, and we are re-affirming rather than making it. A directory with no prior session is genuinely new, the dialog is doing its job, and cctabs leaves it alone with a warning naming the path.cctabs newstill trusts unconditionally: it opens the directory you explicitly named, and requiring a prior session would hang the case that needs it most — starting a session in a repo you just created. - Dismissal is confirmed by a forward signal (the live input footer), never by the dialog text disappearing. The captured buffer is append-only, so the prose stays on screen forever and "is it still there?" always answers yes — a trap that made cleared dialogs read as still-stuck during the debugging of this very bug.
- The skill description was being silently truncated, and the part cut off was the rule that matters. Claude Code caps a skill's description at 1535 characters in the available-skills listing; ours ran 1760, so every session ever shown this skill saw it cut mid-sentence at
"do this in parallel without a new tab", or w…— losing the tail of the anti-substitution rule and the wholeNOT for:line. The description now runs 1432 characters, and the decisive sentence (a subagent is NOT a tab) has moved ahead of the TRIGGER list so it can no longer be the thing that gets dropped. Every trigger phrase is preserved verbatim. The opening clause now leads with the literal tokenscctab/cctabs/terminal tabs, because the first clause is what registers in a listing where this skill can sit 75% of the way down 82 entries. cctabis now a real command. The skill has always advertised "cctab" as a singular alias, but onlycctabswas ever installed — so a session that went looking for the CLI under the name the docs use gotcommand not foundand concluded the tool did not exist.cctabis now a second bin pointing at the same entry point. This matters precisely in the case the listing cannot help with: Claude Code injects the full skill listing once per session and does not re-inject it after a compaction, so in a long session probing the shell is the only route left back to cctabs.
0.5.1 — 2026-08-12
restorecalled a live session's tab dead, and closed it.detectSessionStatusread the Tabby plugin's captured output and treated an empty result as "no session here" — reported asunknown, rendered as "dead tab", and acted on by closing the tab and spawning a replacement. But the plugin fills that buffer by subscribing to each tab's output, and a tab whose session attaches after the subscriber gives up is never captured at all: it reads empty forever while Claude runs happily inside it. The plugin's own log showed 192 tabs wired against 147 subscribed in one boot — 45 live tabs any restore would have declared dead. One of them was a 4,377-turn session whose last turn had finished two minutes earlier. Liveness now comes from the process, not the scrollback:/api/tabsalready reports each tab's pid, so a tab that can't be read but is running is reported and left alone — restore can neither typeclaude --resumeinto it nor close it, so it does neither. Only a tab with no captured output and no process is rebuilt. Backends that don't report pids fall back to the previous heuristic, so an older plugin still works.unknownis renamedunreadablethroughout, because the word was the bug.activemeant "a Claude session exists", not "it's working". Every marker was matched intoactive— including permanent chrome (Claude Code,⏵⏵ auto,new task?) and completion notices (✻ Baked for 47s, which means a turn just ended). On a 61-tab fleet that produced 58activeand 0idle, so the question the README asks — "Did it finish? Is it waiting for input?" — had no answer. Presence and in-flight are now separate: chrome proves Claude is there (idle, waiting for input), and only an unfinished spinner in the recent tail proves it is working (active). The same fleet now reads 2 active / 56 idle / 1 shell. Completion notices are matched by shape rather than by verb, since the vocabulary is open-ended — an enumerated list missedSautéed forandBrewed foron the first real fleet it met.- A tab's permission mode survives a restore. Every restored tab used to come back in whatever the global
claude.flagsproduced, so a tab deliberately left in plan mode came back able to bypass permissions.cctabs sessions --jsonnow recordspermission_modeper tab andrestorehands it back withclaude --permission-mode <mode>, appended after the configured flags so it wins. It composes with--allow-dangerously-skip-permissions, which only makes bypass available rather than selecting it. The mode is read from Claude's own footer rather than from the transcript, because the transcript'spermission-modeentries are written at turn boundaries, not when the mode changes — cycling shift+tab through manual → plan → bypass leaves the recorded value untouched until the next prompt is submitted. Values are validated against what the flag actually accepts (defaultoccurs in real transcripts and is rejected by the flag), and entries with no recorded mode fall back to the configured flags with a count reported rather than silently. Modes round-trip through--manifestonly: a scan-mode restore rebuilds tabs whose sessions are already gone, and a tab with no session has no footer to read. - Plugin: the output-buffer subscriber no longer gives up. It polled for a tab's session for ~5 minutes and then stopped, but Tabby attaches a restored tab's session only once that tab has been focused — which can be hours later or never. Any tab not visited inside that window was left permanently uncaptured, which is the root of the "dead tab" report above. It now polls eagerly for the first minute, then every 5s for as long as the tab lives, stops on destroy, and attaches immediately on focus. Needs a rebuilt
tabby-cctabs; without it the CLI-side pid check already prevents the damage.
0.5.0 — 2026-08-03
- Wave Terminal support is withdrawn. Tabby is the supported terminal. Wave was a working backend through 0.4.x, but it had degraded to the point where a tab would open and the Claude session inside it often never started — a failure that looks exactly like a cctabs bug, costs real time to diagnose, and was only ever reachable on the Wave path. Rather than leave a trap in the install funnel, the adapter is removed:
src/core/wave.tsandsrc/core/wave-db.tsare gone, and running any cctabs command under Wave now exits non-zero with a message pointing at Tabby. Wave is still detected — deliberately — so that message can say "support was withdrawn" instead of "an unrecognised terminal", which would read as a bug. Nothing is lost in the move: Claude conversations live in~/.claude/projects, not in the terminal, socctabs restorereopens them by name once Tabby and its companion plugin are installed.cctabs doctorstill runs under Wave, so a stranded user can see what was detected and what to do about it. cctabs doctor --fixand--yesare gone. Their only action was repairing Wave's orphan-tabid SQLite bug, which left with the Wave backend.doctoris now diagnosis-only: detected terminal (and how — env,CCTABS_TERMINALoverride, or the SSH plugin probe), whether a login+interactive shell can findnode, and the Tabby plugin's health endpoint. The manual SQL for the Wave DB repair is preserved innotes/waveterm-blockslist-orphan-tabid.md.- Fix: the install instructions told you to install a package that doesn't exist. Both the README and the Getting Started guide said
npm install -g cctabs; the published package is@generativereality/cctabs, and the bare name 404s on the registry. Anyone following the npm path off the docs site hit a dead end before reaching a terminal check at all. - Fix: the CLI's own unsupported-terminal error recommended Wave. Landing in an unrecognised terminal printed "cctabs currently requires Wave Terminal" and
brew install --cask wave— funnelling new users straight at the backend that doesn't work. It now points at Tabby, and namessrc/core/tabby.tsas the reference implementation for anyone adding an adapter. - Docs, skill, and installer are consistent about the terminal requirement for the first time: the README,
install.sh, the CLI error text, and the docs site had four different answers between them. - A tab that Claude was actively working in could not be restored. Tabby shows a tab's live terminal title whenever it has no
customTitleof its own — and Claude Code prefixes that title with a spinner glyph while it is busy — so the tab's identity flickered betweencareer-strategyand✳ career-strategydepending on when you looked. Every name-based lookup missed it in the busy state:cctabs sessionsreported no session id,cctabs restoresaidno session found, skippingand left the tab dead, andcctabs sortranked the single most recently active tab last as(no session). Titles are now normalized where they enter cctabs, so a leading status glyph can't change what a tab is. Tabs created bycctabs newwere never affected — they carry acustomTitle— so this only ever hit tabs opened by hand. cctabs sortaccepts--dry, matchingrestoreandresume.--dry-runstill works.cctabs sortis documented — in the command reference, the README, and the skill. It had shipped undocumented everywhere except one changelog line.
0.4.10 — 2026-07-25
- Backend presets inherit into child tabs.
new/resume/forkresolved-bindependently, so a tab spawned from inside a session already running under a backend silently fell back to the plainanthropicpreset. That matters more now that a preset can represent a different Claude account (env_CLAUDE_CODE_OAUTH_TOKEN+env_CLAUDE_CONFIG_DIR) rather than just a different model provider — forgetting-bon a spawned sub-task tab meant it quietly ran on the wrong account. Each launched tab's claude process now carriesCCTABS_ACTIVE_BACKEND=<name>, which its childcctabsinvocations inherit and default to. Explicit-bstill wins, and-b anthropicforces the default back.forkhad no backend support at all before this; it now matchesnew/resume. - Sessions belonging to a second Claude account are no longer invisible. A backend preset can point
CLAUDE_CONFIG_DIRat its own directory (env_CLAUDE_CONFIG_DIRin~/.config/cctabs/config.toml), and every session launched under it lives there rather than in~/.claude/projects. Session discovery only ever looked in the default location, so such a tab was reported asno session named "…" found in any projectbyrestore, andcctabs sessions --jsonemitted it with a nullsession_id— making the manifest unusable for that tab and leaving it to be resumed by hand after every reboot. Discovery now searches the default config dir plus every config dir named by a backend preset (plus whateverCLAUDE_CONFIG_DIRthe current process is running under), acrossrestore,resume,sessions,rename,sort,exportand session-id expansion. Same-name and same-id collisions across config dirs resolve newest-first, consistent with the existing multi-project rule, andrestorenames the account it picked. - …and they relaunch under the right account, automatically. Each discovered session now reports which config dir it came from, which is exactly what identifies its backend — no new per-tab bookkeeping.
restoreandresumecarry that through to the launch, setting the preset's env (or a bareCLAUDE_CONFIG_DIRwhen no preset names the directory) for both the in-place resume and the spawn path. This matters more than a missing-session error would:claude --resume <id>in the wrong config dir doesn't fail, it just can't find the id and opens a fresh conversation — a silent wrong restore.cctabs sessions --jsonnow emitsbackend/config_dir,restore --manifestreads them, andrestoreinfers both from the session id alone when a manifest predates the fields. Socctabs sessions --json | cctabs restore --manifest - --create-missingrestores a second-account tab correctly with no manual flags. cctabs resumepicks the backend from the session it found. Previously the only default wasCCTABS_ACTIVE_BACKEND— the backend of whatever tab you happened to run the command from — which is the wrong answer whenever the session you're resuming belongs to a different account. Precedence is now explicit-b, then the session's own config dir, then the inherited one; the success line says which ([backend: gapminder (from session)]). Resuming a second-account session no longer needs-band-sspelled out by hand.restoreis now one implementation.cctabs restore [dir]andcctabs restore --manifesthad grown into two divergent code paths that made different decisions about the same tab. The bare scan now builds entries from the tabs it finds and runs them through the same planner as manifest mode, so resolve → attach → spawn → reorder → summary exists exactly once. Behaviour converges on the better half of each: manifest mode gains the scan's dead-tab handling (a tab whose terminal is confirmed gone is rebuilt around the resume instead of having a command typed into a shell that isn't there) and its duplicate-name dedup, while the scan gains manifest mode's worktree-aware, newest-wins session lookup — socctabs restore <dir>with two same-named sessions now resumes the newest instead of skipping the tab as ambiguous. A dead tab whose name is already live in another tab is no longer restored into a second copy of the same session.- Fix:
--dryno longer promises restores a real run wouldn't perform. In the scan path a dry run skipped both the empty-scrollback confirmation and the duplicate-dead-tab dedup, so it happily listed tabs that a real run would instead have closed as duplicates, and reported "would send" for tabs a real run would rebuild. Planning is now a single read-only pass shared by both modes, with--drystopping immediately after it — the dry output is the real run's decisions, and planning cannot mutate a tab by construction (there is a test that fails if it ever tries). - Fix:
restore/resumeno longer mistake a longer-named tab for the session's own. Tab resolution falls back to prefix matching, so resuminggapmindermatched a livegapminder-logintab and skipped the resume with "already running" for a session whose tab wasn't open at all. Deciding whether a session's tab already exists is now exact-name (or full-id) matching in bothrestoreandresume; hand-typed lookups (send,close,rename,fork,scrollback,export) keep the prefix convenience. - Faster
restoreon Tabby: missing tabs spawn in parallel again — safely this time. The two restore paths disagreed about whether parallel spawning was safe, and the pessimistic one was right: a Tabby tab only spawns its process once its terminal frontend attaches, which only happens once the tab has been focused, andAppServiceemits that focus event asynchronously against whichever tab is active by then. Fire several creates in one turn and the losers never start Claude at all. The plugin now serialises tab creation internally and doesn't answer until the new tab's process is actually running, advertising this as thespawn-waits-for-ptycapability on/api/health; restore probes for it and only then spawns in parallel, falling back to the old serial-with-settle pace against an older plugin. Needs the rebuilttabby-cctabsplugin — without it nothing breaks, restore is just as slow as before.
0.4.9 — 2026-07-22
- Fix:
restore --manifestnow rebuilds the tab bar in manifest order. Tab-order restoration (added in 0.4.5) only ever ran in the no-manifest name-scan path; arestore --manifest --create-missingspawned the missing tabs appended to the end of the bar (and, on Tabby, spawned in parallel so even their relative order was nondeterministic), never re-applying the order the manifest carried. Manifest mode now records each entry's final tab id (existing match, current tab, or freshly spawned) and callsreorderTabsas a separate final step after all spawns complete — the parallel spawn is left untouched. Skipped under--dry. (Internal: the no-manifest path'srunLegacyModeis renamedrunNameScanMode— it was never deprecated, just older than manifest mode.) - More robust
restore/resumepicker handling, plus the follow-up mobile-app overlay. Under heavy load (e.g. right after a largerestore) a tab could stay stuck on Claude's "Resume from summary / full session" picker, and cctabs knew nothing about the "Continue coding in the Claude mobile app … Enter/Esc to close" remote-control info overlay that can paint immediately after the session loads — leaving the tab on an overlay rather than a clean prompt.confirmResumePickernow (a) waits adaptively for the picker (patient under load, but early-exits the moment the session is demonstrably loaded without one, so the common no-picker resume no longer burns the whole window), (b) retries the confirm more times for a slow load, and (c) sweeps for the mobile-app overlay — on both the picker and direct-resume paths — and dismisses it with Esc. The send-↓-once / retry-Enter-only safety is preserved, so a retry can never land on option 3 ("Don't ask me again"). cctabs renamenow persists the new name to disk soresumecan find it. Previouslycctabs rename(and Claude's in-session/rename) only relabelled the live tab / remote-control session, never thecustomTitlerecorded in the session's.jsonl— which is whatcctabs resume <name>/restoresearch by. A session renamed that way became unfindable by its new name.cctabs renamenow also appends acustom-titleentry (the same line shape Claude writes at launch) to the resolved session's transcript, socctabs resume <newName>works afterwards. Claude's own/renamestill doesn't touch disk — that remains a documented limitation (see the "Two names" section of the skill).- Fix:
restorecould send a session into a directory with no transcript at all, if the agent had evercd'd into a subdirectory. Regression in 0.4.8's own fix (see below). A session's recorded cwd changes for two different reasons: (a) the session was genuinely relaunched from a new directory (0.4.8's case — the transcript moves with it), or (b) the agent rancd <subdir>via the Bash tool mid-session, which drifts the per-message cwd without the transcript file ever moving. 0.4.8 couldn't tell these apart and would sendrestore/resumeinto the drifted directory, failing with "No conversation found with session ID: ...". BothresolveTabSessionandfindSessionsByNameGloballynow only accept a recorded cwd whose own project slug matches the directory the transcript is actually stored under — a drifted cwd is skipped in favor of an earlier, matching one.
0.4.8 — 2026-07-18
- Fix:
restorecould try to relaunch a session into a directory that no longer exists. Session cwd was resolved from the first recorded location in a session's transcript, not the most recent one. That's wrong once a session's working directory has changed mid-life — most commonly: a--worktreetab's worktree gets deleted, and the session is later manually resumed from the repo root instead. A subsequentcctabs restore(plain or--manifest) would then try tocdback into the deleted worktree path, ignoring the relocation. BothresolveTabSessionandfindSessionsByNameGloballynow track the last recorded cwd, matching the existing last-wins handling for renamed sessions (customTitle). This fix had its own regression — see 0.4.9 above, fixed in the very next release.
0.4.7 — 2026-07-04
- Drive a remote Tabby over SSH. cctabs can now open / list / close / send tabs on another machine's Tabby over SSH. Over SSH the parent terminal never exports
TERM_PROGRAM, so cctabs previously refused with "unrecognised terminal" even though the target host's cctabs plugin was running and reachable on127.0.0.1:3300. It now (a) auto-falls back to probing that plugin when environment detection comes up empty — so a baressh host 'cctabs new foo "~"'just works when the remote plugin is up — and (b) honours an explicitCCTABS_TERMINAL=tabby(aliasCCTABS_BACKEND) override to force the backend regardless ofTERM_PROGRAM.cctabs doctorreports the resolved backend and how it got there (override vs plugin probe). The probe only runs when detection is otherwise unknown, so a recognised local terminal never pays the network round-trip. - Per-install name prefix for new sessions. A new
defaults.prefixconfig setting (empty by default) is prepended to both the Tabby/Wave tab title and theclaude --namefor every name minted bynew,resume, andfork. Set it to disambiguate a machine when several share one claude.ai remote-control session list, where unprefixed names otherwise collide.
0.4.6 — 2026-06-30
- Fix:
cctabs new --worktreeno longer spawns at the wrong commit. Previously cctabs delegated worktree creation toclaude --worktree <name>, which can branch from the upstream tracking ref (or another unexpected commit) when local commits aren't pushed — silently producing a worktree at a stale base. Now cctabs runsgit worktree additself, explicitly anchored to the target dir's current HEAD, then launches plainclaudeinside the worktree. The success line prints the base SHA so you can verify it's what you expect. If a branch namedworktree-<name>already exists, cctabs checks it out at its existing tip and warns when that differs from HEAD.
0.4.5 — 2026-06-09
- New tabs open right after the active tab (Tabby).
new/fork/resumepreviously dropped the new tab at the far end of the bar; it now lands immediately after the tab you created it from, browser-style. (Wave keeps append behaviour.) - Restore rebuilds the pre-reboot tab order (Tabby). Recreated dead tabs were appended, scrambling the original layout; restore now captures the pre-reboot order and reorders the bar to match once every tab is back. Both positioning changes need the rebuilt
tabby-cctabsplugin to take effect — with an older plugin the CLI degrades gracefully (tabs append as before). - Restore now auto-advances the "Resume from summary / full session" picker. When
claude --resumereattaches a large or old session it shows a blocking three-way picker; previously arestoreleft every such tab stuck on it (the auto-confirm logic only ran when seeding an initial prompt, which restore doesn't).restorenow detects the picker and selects option 2, "Resume full session as-is" — the whole point of restore is to bring the conversation back intact, not a lossy summary. It moves the cursor down exactly once (never risking option 3, "Don't ask me again", which would permanently silence the prompt) and retries only the confirm. - Restore no longer double-creates tabs that share a name. After a reboot it's possible to have two dead tabs with the same name; restore was recreating each, spawning duplicate live tabs that both resumed the same (newest) session. Restore now keeps the first tab per name and closes the extras.
0.4.0 — 2026-05-16
cctabs export+cctabs import— move tabs and their Claude sessions between machines.cctabs export <tab>(or--allfor the whole workspace) bundles each tab's Claude conversation jsonl plus a small manifest into a.tar.gz. On the other machine,cctabs import <archive>extracts the jsonls into the local~/.claude/projects/<target-slug>/and opens a tab thatclaude --resumes each session. The target's Claude project slug is recomputed from the resolved target cwd, so cross-machine$HOMEdifferences just work. Worktree-backed tabs (cctabs new --worktree) are handled correctly — export falls back to scanning<cwd>/.claude/worktrees/*when the direct slug lookup misses, and records the actual worktree path in the manifest so import recreates the right slug on the target.--cwd <path>onimportremaps a single-tab archive to a different directory.--dry-runpreviews everything without copying files or spawning tabs.--forceoverwrites a session jsonl that's already present locally.- Uses the system
tarbinary — no new npm deps. - Note: jsonl contents are not rewritten on import. Absolute paths from the source machine remain in the conversation history as historical references; Claude adapts to the actual current cwd on resume.
0.3.2 — 2026-05-13
- Fix:
cctabswith no arguments crashed. The default command (which dispatches tosessions) was passing a fake context shaped like{ args: {} }tosessionsCommand.run, butsessions --json(added in 0.3.1) readsctx.values.json, so the missingvalueskey threwCannot read properties of undefined (reading 'json'). Regression from 0.3.1.
0.3.1 — 2026-05-12
- Tabby: active-session detection now survives viewport padding and spinner redraws. Previously, Tabby tabs in the middle of a long Claude turn were misreported as
terminal/unknownbecause the scrollback window only sampled the last 10 rows — Claude's animated status line lives further up. The detector now scans a 200-line tail and matches against spinner labels (Thinking,Composing,Worked for…, etc.) and Claude's brand glyphs. cctabs new --resume <name>to open a tab and resume a named session in one step.- Manifest-driven restore. Each tab writes a manifest under
~/.cctabs/, socctabs restorecan reopen every tab and resume every Claude session after a reboot — no need to remember names. cctabs send --wait-for-promptwaits until the tab is at a Claude prompt before delivering input, instead of racing the previous turn.cctabs sessions --jsonfor scripting.- Skill: sharper triggers. The Claude Code skill now lists explicit trigger phrases ("open a new tab", "in another tab", "fork this tab"…) and disambiguates "tab" from the Agent tool, so Claude stops spawning background subagents when you asked for a real terminal tab.
0.3.0 — 2026-05-10 (not tagged; folded into 0.3.1)
- Tabby Terminal support. cctabs now works on Tabby in addition to Wave. Install the companion plugin (
tabby-cctabson npm; available from Tabby → Settings → Plugins) and run the CLI normally — the backend is auto-detected. cctabs doctorprints a diagnostic of the current backend, the running plugin, and known orphaned tab-ids — useful when Wave or Tabby state drifts out of sync.cctabs new --backend <name>for Ollama, Kimi, Qwen, and local-model presets. Spawns Claude Code wired to the chosen backend without per-tab env juggling.
0.1.3 — 2026-04-25
cctabs restoresearches every Claude project directory by default instead of only the current working directory. Restore now Just Works after a reboot regardless of which terminal/cwd you start it from.- Survive Wave restart with ephemeral workspace. Resume keeps working across Wave restarts; the workspace state no longer becomes stale.
--sessionaccepts prefixes, so you can resume by partial name.
0.1.2 — 2026-04-22
- Prefer exact tab-name match over prefix match in tab resolution. Stops
cctabs send apifrom accidentally hittingapi-v2. - Recreate dead tabs on resume + handle spaces in project paths.
- Bump wsh
blocks listtimeout socctabs newno longer flakes on large Wave sessions. - Skill: parallel-work guidance + worktree decision guide.
0.1.1 — 2026-04-10
- Skill renamed from
herdtocctabsto match the package name everywhere.
0.1.0 — 2026-04-10
- Initial public release:
cctabs new,fork,close,send,sessions,scrollback,restorefor Wave Terminal. - Claude Code skill (
cctabs) lets Claude orchestrate its own sibling sessions.