Skip to content

Canopy v1.2.0

Latest

Choose a tag to compare

@github-actions github-actions released this 08 Aug 09:00
· 3 commits to master since this release

Added

  • Worktree sessions are named in Claude Code (#62, #63): Canopy passes
    --name, so a session is identifiable by its branch in Claude's prompt box
    and /resume picker instead of falling back to an auto-generated label.
    A newly created worktree session is named <repo>-<branch>; reopening an
    existing worktree uses the branch alone. Branch names are treated as hostile
    shell input — git refs permit characters a shell would act on — so they are
    sanitized and quoted before use. Plain sessions and detached-HEAD worktrees
    get no name: the former are renamed asynchronously and would race, the latter
    have no branch worth naming.
  • "Needs input" session state (#52, #54): a session blocked on a permission
    prompt now shows an amber dot with a raised hand, and the status bar leads
    with "1 needs input". Previously the PTY went silent while Claude waited, so
    after five seconds the session was declared finished — checkmark, "Session
    finished" notification — and three seconds later decayed to grey, making a
    stalled agent indistinguishable from an idle one. Canopy now asks Claude Code
    directly (claude agents --json) instead of inferring from silence, so it can
    tell a permission prompt from a finished turn. Requires a backend that runs
    Claude on the host (Off or Claude sandbox); other backends keep the old
    heuristic. A separate Notify when sessions need input setting (on by
    default) fires even when Canopy is in front — unless you are already looking
    at that exact tab — since the blocked session usually is not the one on screen.
  • Claude sandbox (Bash only) (#53): a third sandbox option, using Claude
    Code's own macOS Seatbelt sandbox. Nothing to install — no image, no daemon,
    no Docker — and session resume works. It is deliberately the weakest of the
    three: it confines Bash only (Read/Edit/Write go through the permission
    system), and while writes are limited to an allowlist, reads are filtered
    only by a deny-list, so ~/.ssh and ~/.aws/credentials remain readable.
    Canopy sets allowUnsandboxedCommands: false, because the CLI defaults it to
    true: without it, a command the sandbox denies is simply retried outside the
    sandbox and auto-approved by --permission-mode auto, which would make the
    setting advisory. The "Bash only" part is architectural — Claude Code wraps
    each shell command in sandbox-exec, so the profile covers the child process
    and not Claude itself. Apple container remains the strongest option: it is
    the only backend that confines the whole Claude process. Every sandbox picker
    now describes what its backend actually protects.
  • Per-session Claude flags (#55): the New Worktree Session sheet has a flags
    field that overrides the project and global values for that session only, so
    "fable on the hard branch, opus on the chore branch" no longer means editing a
    project-wide setting that hits every sibling. Free text rather than pickers —
    --permission-mode gained dontAsk and renamed defaultmanual in 2.1.200,
    and every enum the CLI owns is a maintenance subscription.
  • Model and reasoning effort per turn in the transcript (#56): each Claude
    turn is labelled with what produced it (e.g. opus-5 · xhigh). Copy includes
    it, so a transcript pasted into an issue says which model wrote which turn.
    Per-message on purpose: the model can change mid-conversation via /model,
    and effort is legitimately absent on older CLIs and on models without the
    concept.
  • Sandbox and Claude flags in Session Info (#73): a running session had no
    way to show how it was actually isolated or which flags it launched with —
    both are per-session overridable, and a chosen backend can silently fall back
    when its prerequisites are missing. The flags row includes the --add-dir
    Canopy injects for worktree sessions, not only what you configured.

Changed

  • Claude session IDs are assigned, not discovered (#61): a new session is
    launched with --session-id <uuid> and resumed with --resume afterwards,
    instead of Canopy guessing which conversation a tab owned by scanning
    ~/.claude/projects/ for the most recently modified transcript. A tab is now
    bound to its own conversation from the moment it starts, so running claude
    yourself in the same directory cannot hijack it — and the transcript viewer
    and token counts work immediately, where before they were empty for the
    entire life of any session Canopy created.
  • Docker Sandbox (sbx) is labelled legacy: it is the only backend without
    session resume, and Claude sandbox now covers the zero-install case. It still
    works and is not deprecated. (#53)
  • The project view leads with cross-worktree state (#60): the worktree list
    and a collision summary come first; "New Worktree Session" moves into the
    header. Claude Code creates worktrees itself now (claude -w, /fork,
    isolated subagents), so creation is no longer the differentiated part —
    watching several at once is, and nothing upstream reports collisions.
  • SwiftTerm 1.15.0, removing the hover-motion workaround. Upstream fixed the
    SGR encoding bug (migueldeicaza/SwiftTerm#520) that made buttonless hover
    motion read as a button release, so Canopy no longer swallows mouseMoved
    while any-event tracking is active. Hover highlighting in Claude Code's
    fullscreen menus works again, and the Cmd-hover link preview is no longer
    suppressed. (#48)
  • scripts/bundle.sh no longer dirties the working tree (#64): it restores
    the committed BuildInfo.swift on exit unless --release is passed. Running
    it after every change — which CLAUDE.md instructs — used to guarantee a dirty
    generated file that git add -A swept into unrelated commits. New --dry-run
    stops before the archive.
  • CI runs on every pull request, not only those targeting master. A
    branches: filter meant stacked PRs got no CI at all, so code could reach
    master having only ever been built locally.

Fixed

  • A tab no longer adopts an unrelated Claude conversation (#51):
    loadSessions overwrote every stored session ID on launch with whatever
    transcript in that directory was newest, so running claude yourself in a
    worktree — or opening a second tab on it — silently re-pointed the tab, and
    Canopy then --resumed into that conversation. An established session ID is
    user data; it is now filled in only when missing.
  • git works inside Docker Sandbox worktree sessions (#59): only the worktree
    was mounted, so its .git file pointed at a path that did not exist in the
    sandbox and every git command failed with "not a git repository", with
    nothing surfaced by Canopy. The main repository is now passed as an extra sbx
    workspace.
  • Worktree sessions can read the main repository (#58): mounting fixed the
    filesystem, but Claude Code's own tool boundary is scoped to the working
    directory independently of what is mounted, so a main-repo read was refused
    under --permission-mode manual. Canopy now passes --add-dir.
  • The sidebar shield names the right backend: it was a binary test between
    two backends, so a Claude sandbox session displayed "Running in Apple
    container" — claiming stronger isolation than it had.
  • Tests no longer read your real settings: AppState(configDir:) isolated
    sessions and projects but not settings.json, so a test could pass locally
    and fail in CI for reasons invisible in its body. (#66)
  • Claude-created worktrees under .claude/worktrees/ are verified to flow
    through worktree listing, collision reports and the unmerged-commit warning.
    The hypothesis held, so the deliverable is the tests. (#57)