v1.11.2
[v1.11.2] - 2026-08-31
Bug Fixes
- The read-only worktree planning proof now ignores Git's transient bookkeeping wherever the fixture repository sits beneath its snapshot root. Background maintenance can create or remove lock files without Sidecar doing anything; the proof continues to compare every path Sidecar owns while no longer treating Git's internal locks as product mutations.
[v1.11.1] - 2026-08-31
Bug Fixes
-
Release verification no longer intermittently loses its private tmux server between agent-control integration tests. The suite now keeps one inert session alive for the package lifetime, so one test cleaning up its last working session cannot race the next test's server startup. The package still uses its own socket and tears down only that isolated server.
-
The remote-host reconnect test now observes a stable recovered connection instead of a transient state. Its successful fake stream stays open like the real host protocol, so loaded CI cannot miss the online state between an immediate end-of-stream and the following reconnect.
[v1.11.0] - 2026-08-31
Features
-
Shells lost to a tmux restart come back. After a reboot or a tmux server crash, Sidecar recreates the managed shells that were running, under their own names and in their own working directories, once the first frame is on screen.
sidecar session statusshows what it would do before it does anything — every shell named as reattach, recreate-shell, resume-agent, manual, skip or refuse, with the reason and whether it would run an agent — andsidecar session restoreperforms exactly that plan, with--dry-run,--shell,--agentsand--yes.sidecar session policysets it per shell (--inherit,--shell,--resume,--never), so a long-running server, a disposable helper and a sensitive agent session can differ without changing the machine default. Nothing arbitrary is replayed: a--runcommand, dev server or test watcher is never restarted, a working directory that no longer exists is a refusal rather than a fallback to some other directory, and a tmux session name held by something else is a refusal rather than something Sidecar closes to take the name. Conversations are a separate decision from terminals:plugins.workspace.sessionRestore.resumeAgentsdefaults toask, so a reboot restores your shells and then asks once, in one grouped summary, before resuming anything that can spend money or change a repository. (td-e78e17) -
A Sidecar-managed shell can be bound to the exact agent conversation running in it. A provider's own hook calls
sidecar agent report-session --kind KIND (--id ID | --path ABS_PATH), and Sidecar records which native conversation that pane is in. That binding is what makes a cold restart able to offer to resume that conversation, and what makessidecar agent read --source transcriptreturn it. Sidecar never guesses one: an unbound shell getstranscript_unavailable, because "the newest conversation in this directory" is wrong often enough to matter and looks identical to being right. Session values are redacted by default —agent listandagent getreport only whether a shell is bound and whether an official integration vouched for it, and the value appears for your own shell or with--include-session-ref, since list output routinely lands in logs and CI artifacts.shells.jsonmoves to schema version 3 to hold the binding, additively: a record that has never run an agent serializes exactly as version 2 wrote it. (td-8ec2cc) -
Codex and Claude Code can tell Sidecar which conversation they are in.
sidecar agent integration install codex(orclaude) adds one Sidecar-owned entry to that provider's own hook configuration, and from then on each new session reports its identity to the shell it is running in. They install at session-identity tier and stay there: these hooks say which conversation is running, never what state it is in, so screen and process detection remain the only authority for whether an agent is working, blocked, or done. Installation preserves every unrelated hook and every unrelated setting,--dry-runshows the exact ops, and uninstall removes only Sidecar's entry. Codex needs its hook trusted before it will run: Sidecar writes the trust record itself, and if that ever stops matching, the failure is Codex's own visible one-time "Hooks need review" prompt rather than a hook that silently never fires. (td-8ec2cc) -
Every agent resume command now comes from one registry. The Conversations plugin had the only table of how to resume each provider, as a switch building shell strings. It now lives in
agentcatalogas structured argv, so the Conversations UI, the CLI, and cold restore share it and cannot drift, and a session identifier is an argument vector entry rather than text spliced into a command line. A reported identifier that would read as a flag is refused outright, both when it is validated and again when a resume is built from it. The UI keeps its current user-confirmed behavior and the command you see is unchanged. (td-8ec2cc) -
Agent state can come from deterministic provider integrations instead of screen inference alone. The new lifecycle contract records working, idle, blocked and terminal outcomes in a bounded JSONL store, resolves competing sources through one authority policy, and exposes the result through
sidecar agent report,end,releaseandexplain. The bundled OpenCode integration reports full lifecycle state; Codex and Claude Code report session identity. Install, inspect, update, repair and remove integrations from Configuration or the matchingsidecar agent integrationcommands, all through the same service. Integrations report lifecycle facts only: they never send prompts, responses, tool data, paths, credentials, or notification policy. (td-43a93f) -
A managed agent can be driven without taking over its terminal. Behind the
agent_controlfeature flag,sidecar agent start,prompt,wait,readandsend-keysoperate on one pinned shell, validate the complete request before writing anything, and refuse when the pane is busy, replaced, blocked, or owned by something else. Prompt delivery uses the same ordered paste and key encoding as the embedded terminal, waits use a pooled tmux control client with bounded polling fallback, andreadoffers visible, recent, unwrapped, detection and transcript sources. This is the non-interactive counterpart to watching and answering an agent in the TUI. (td-7de1af) -
A pane you have opened can be moved.
Mfrom any pane — or the new⊞button on the pane header, left of the close×— opens one reposition modal:h/j/k/lmove a draft of the layout,zzooms,entercommits the whole sequence atomically,escdiscards it. The pane is pulled out and grafted back, so its tabs, scroll position, selection and any live terminal travel with it, and so does the share of the box you dragged it to. All three pane hosts have it: project Workspaces, the global Sessions browser, and the content decks beside Files, Git and Notes.Mwas chosen overm, which is alreadyrenderandmerge-workflowin two of the twelve pane contexts, and one key must mean one thing in every pane. (td-2ec104) -
sidecar layout movegives agents the same capability in one call.sidecar layout move 2.1 --to 1.2moves by cell,--to 3appends to a column (opening one past the last), and--focused --to left|right|up|downuses the identical direction rule the modal's keys compile through, so the CLI and the keyboard cannot drift. It was always possible to rearrange a layout withlayout getpluslayout apply --spec, but that means reconstructing every pane on screen to move one of them, and every pane you reconstruct is a pane you can get wrong. Like get and apply it never queues, refuses rather than squeezing, and reports a move with nothing to do asunchanged(exit 0) rather than calling it moved. With--sessionsit changes this machine's viewer tree even for a row whose workspace is on another host, and sends no layout mutation to that host. (td-2ec104) -
Three shell verbs stopped being things only the TUI could do.
sidecar shell rename --target <session>renames a shell you are not sitting in — until now rename resolved "which shell am I" from the ambient tmux environment, so there was no way to rename any other one.sidecar shell send --target <session> --run/--type <command>sends a command into an existing shell, matching the--run/--typesplitcreate shellalready had. Andsidecar create worktree --planresolves a worktree plan and prints it as JSON without creating anything, so a caller can show branch, path, source OID and whether a setup hook will run before committing to it. Sidecar ownsshells.json, so a capability reachable only through the TUI was a gap for every agent, not only for remote hosts.shell sendguards its own boundary: the underlying send-keys has no protection beyond a blank-command check, so the verb refuses a session that is not a live record or a registered worktree session for the resolved project, and refuses one recorded on a different tmux server rather than typing into whatever answers that name on this one. (td-677dde) -
A configured project can be used before it has ever been opened.
--projectresolved only through project state directories that already existed on disk, so a project listed inconfig.projects.listbut never opened returnedunknown project. It now falls back to the configured list and registers the project the same way first-open does. (td-677dde) -
Sidecar can watch and drive sessions on another machine over SSH. Behind the
sidecar_remote_hostsfeature flag, installing Sidecar on the host and registering it withsidecar host addmakes that machine's projects, shells, worktrees, agent states and live panes appear in Sessions beside local ones. Selecting a row opens the real remote tmux pane with history, search, selection and ordered input intact; cross-host geometry leases let either machine take back its own viewport by typing. There is no daemon or listening port: Sidecar starts an ephemeral host process over SSH stdio, reuses tmux control mode for pane traffic, and names unreachable, missing-binary, missing-tmux, login-output and version-skew failures with their fixes. The Remote Hosts configuration page andhost add/list/set/remove/probecommands share one validated registry and apply changes without an app restart. (td-f10d6f, td-998e58, td-42b724, td-141917) -
Notifications can reach you inside Sidecar and outside the terminal. Alerts now land in a persistent JSONL-backed notification centre with stacked toasts, an unread header indicator, keyboard and mouse actions, target links, per-source rules and a
sidecar notifyCLI for posting, listing, dismissing and configuring them. Native macOS and Linux notifications, built-in or custom sounds, quiet hours, background-only delivery and live config reload all run behind delivery adapters. A Sidecar running through SSH can forward a remote transition to the viewing machine or emit through the outer terminal, while deduplication and claim rules prevent the same event from alerting twice. (td-7b9ccc, td-eb6475, td-58679c) -
Agents on another machine can be driven the same way as agents on this one. Every
sidecar agentverb —list,get,start,prompt,wait,read,send-keys— plussession statusandsession restorenow take--host IDand run on that registered host, as onesidecar <verb> --jsoninvocation over the ssh connection Sidecar already keeps open. There is no second protocol and no new daemon: the verbs were headless, target-taking and--jsonfrom the start, so carrying them to another machine is transport rather than design. What you get back is the host's own answer, including its refusals — a blocked agent on another machine reportsagent_blockedwith the host's own sentence, not a local approximation of it, because the rules run on the machine that owns the pane. Two failures the local vocabulary could not describe honestly get their own codes:host_unavailablewhen the machine could not be reached at all (nothing was attempted, so trying later is the fix) andversion_skewwhen the host's Sidecar does not know the verb (update one of the two binaries). Conversation identifiers stay where they belong: a remoteagent listoragent gettells you whether a shell is bound and whether an official integration vouched for it, never what it is bound to, unless you ask with--include-session-ref. Nothing remote is ever written into this machine'sshells.json. A remote verb requires an explicit target, because the "current shell" shorthand names a shell on this machine and two machines running a same-named project generate the same tmux session names. Cold restore runs on the host too — a viewer asks for it and watches, and never rebuilds another machine's state locally. Behind the existingsidecar_remote_hostsandagent_controlflags. (td-a55114)
Bug Fixes
-
A tmux server crash no longer erases your shell records. When a tmux server dies, every managed session disappears in the same instant. Sidecar's liveness check asked, once per shell, "is this one gone?" — got a true-looking answer every time, and tombstoned the whole file. That is how a crashed server took a user's
sidecarandbraidshell lists with it. The liveness path no longer asks for a deletion in that situation at all: when no tmux server is running, or when a shell's last confirmed server is not the one running now, the record is kept and marked as something a restore can bring back. A shell that exits inside a server that is still up is still tombstoned, and still recoverable withsidecar shell restore, because that is a terminal you closed. Both paths that could delete were fixed, including the project workspace one, which reaped per shell with no protection at all. (td-e78e17) -
A late report from an agent that has already exited can no longer overwrite its successor's session binding. This one was found by building the proof rather than by a test. The generation a reporting hook derives for itself falls back to the pane's root process when it cannot trace its own ancestry back to the pane, and the independently-derived "what occupies this pane now" falls back to the pane's root process when nothing is running there. Those are exactly the two conditions that hold for a hook left behind by a provider that has exited: it has been reparented away from the pane, and the pane is empty. Both sides fell back, compared equal, and the stale report was accepted — in precisely the case the check exists to reject. The reporting side now has no fallback: a hook that cannot prove which provider it belongs to is refused rather than believed. Two fallbacks agreeing is not evidence. (td-8ec2cc)
-
agent promptwith the text left off no longer prompts your own shell with a shell's name.sidecar agent prompt reviewerreadrevieweras the prompt text and typed it into the shell the caller was sitting in, because one positional argument means "prompt this shell". A lone argument that resolves to a managed target is now a usage error naming what is missing. Empty or blank prompt text is a usage error too — it exited 5, the code for a semantic refusal, where every other malformed command line exits 2, and a caller could not tell "I built this wrong" from "the agent would not take it". (td-2cea15) -
layout apply --jsonandlayout move --jsonwrite only JSON to stdout. Both wrote the structured result object and then appended the human per-pane lines after it, sosidecar layout move 2.1 --to 1.2 --json | jqfailed on the trailing text — which is the only reason to ask for--jsonat all. The flag's own help already promised "one structured result object to stdout", andlayout get --jsonalready kept that promise. The two projections are alternatives now: without--jsonyou get the human lines and nothing else, with it you get the object and nothing else. Both carry the same per-item verdicts, cells and reasons, so nothing is lost by choosing one. (td-0e2d12) -
The reposition modal asks before discarding a live inline edit. Opening it released every input surface the content deck owns, and for an inline edit that release killed the tmux session holding an unsaved buffer without a word. Every other caller of that release is a surface teardown — a plugin switch, a scope change, shutdown — where the editor has nowhere left to be drawn; the modal is the one caller that keeps the deck on screen and returns to it, so nothing forced the buffer to die. It now raises the editor's existing Save/Discard/Cancel dialog, the same one clicking away from the editor raises, and opens the modal on the pane you asked for once you have answered. Both doors onto the modal —
Mand the header⊞— inherit it. (td-0e2d12) -
A power loss during an agent lifecycle report no longer costs two reports instead of one. The lifecycle log is append-only JSONL, and a machine dying mid-append leaves a final line with no newline. The next report was appended straight onto that fragment, welding two records into one line that neither could be read from — so the crash cost the report it interrupted and the next healthy one, while the write reported success. A pane that had reached its third report came back believing it was on its first. The log is now re-framed when it does not end where a line should. (td-b2370e)
-
Validation errors no longer exit with the code that means "you passed a bad flag."
create worktree,create shell --nameandshell rename --targetreturned exit 2 both for an unusable command line and for a perfectly well-formed one whose value was rejected — a branch that already exists, a display name already in use. A caller cannot tell those apart, and the remote-host viewer read the second as version skew and told the user to upgrade Sidecar. Input rejection is now exit 5; exit 2 keeps meaning a usage error. Documented in each command's exit-code table. (td-677dde) -
A display name or worktree name starting with
-works.shellstate.NormalizeNameaccepts a leading dash, so-wipwas a legal name that the argument parser then read as an unknown option.shell rename --target,shell sendandcreate worktreenow stop flag parsing at--. (td-677dde) -
notify config setis inside the isolation gate. It rewrites the whole config file but carried no mutating mark, leaving it the one mutating verb a misconfigured proof run could still drive against the real~/.config/sidecar/config.json. (td-677dde) -
Embedded terminal backgrounds no longer flicker, truncate, or flood at particular widths. Captures now preserve blank rows with
capture-pane -N, every drawn cell takes its background from tmux, and Sidecar paints a row's carried background through its trailing cells without letting pane padding leak into the child's pen. The old content-based canvas heuristic is gone, so a terminal's colour no longer changes because its text or width happened to trigger a guess.
Remote hosts (behind features.SidecarRemoteHosts, default off)
-
Phase C: a remote host can be changed, not only watched. Creating a shell, creating a worktree through its confirmation, seeding an agent, and renaming either kind now work on a remote row from the Sessions browser. Mutations run as one-shot
sidecar <verb> --jsoninvocations over the ssh connection that already carries the observation stream, so the serve protocol stays one-directional and read-only by construction. Rows arrive through the host's next snapshot rather than being invented locally. Delete, merge and navigation still refuse — their implementations resolve paths against the local filesystem. That was also a live bug inO(open in Git), which had no guard at all and sent a remote path into a local worktree switch; on a machine with the same checkout layout that succeeds against the wrong repository. (td-677dde) -
A confirmed worktree plan is pinned to its commit.
create worktree --expect-source-oid OIDrefuses with exit 5 when the base ref no longer resolves to the confirmed commit, and the remote confirmation passes its plan's OID back on Create — so an agent pushing to the branch while the user reads the modal yields a refusal naming both commits, not a worktree silently built from the new head. The local modal already had this guard from executing its stored plan; the remote path re-ran the command from raw arguments and did not. Also from the pre-merge review: a setup hook failing with its owncommand not foundis no longer misread as an uninstalled Sidecar, a login profile emitting 32+ structured-log lines can no longer push a successful result out of the decode window, host-derived error text is stripped of terminal escape bytes before display, disabled hosts refuse mutations up front by name instead of failing as "removed or retargeted", the rename modal shows an in-flight state and swallows the double Enter that raced two renames on the host, a stale reply from one host no longer clears another host's pending selection, switching the create form from a local to a remote project clears the local repo's branch list, and Merge is hidden on remote rows rather than offered and then refused. (td-677dde)