Releases: plumbkit/plumb
Release list
v0.18.1
Changelog
Features
- e975b30 feat(cli,tools): degraded recovery retries itself, and both packets disclose linkage truth
Fixes
- b94acd4 fix(cli): refill a blank durable external linkage from the session file on a full restore
- f46faff fix(cli,tools): close the review blockers — race-free retry injection, live heal arm, self-replay scan
Others
- c4e4b21 Keep delivered notes as a transcript: note_ttl_minutes, keep_delivered_notes, backlog counts
- e1ee2f8 chore(release): cut 0.18.1
- 84c0342 docs: apply the design-review erratum — griefing vector named, retry-converged establishment trigger
- 2b8fffd docs: move retention entry to unreleased
- c70807c test(sessionstate): pin the RepairExternalID conditional-repair contract
- b883269 test(smoke): identity coverage matrix — full IDs, credential privacy, mail and reboot scenarios
- f490339 test(smoke): stop and reap serve before reboot replacement
- 4e30ff3 test(smoke): wait for isolated daemon teardown
Install
Homebrew (macOS + Linux):
brew install plumbkit/plumb/plumbDirect download: grab the archive for your OS/arch below, extract it, and
put the plumb binary on your PATH.
| Platform | Archive |
|---|---|
| macOS, Apple Silicon (M-series) | plumb_0.18.1_darwin_arm64.tar.gz |
| macOS, Intel | plumb_0.18.1_darwin_amd64.tar.gz |
| Linux, Intel/AMD 64-bit | plumb_0.18.1_linux_amd64.tar.gz |
| Linux, ARM 64-bit | plumb_0.18.1_linux_arm64.tar.gz |
Verify your download against checksums.txt.
macOS note: these binaries are not yet code-signed. On a direct download
Gatekeeper may block the first run — clear the quarantine flag once:
xattr -dr com.apple.quarantine ./plumb. Homebrew installs are unaffected.
v0.18.0
Changelog
Features
- 9cadcf2 feat(cli): fail closed on a contested connection (issue #182)
- f4de91a feat(serve): start unattached — session_start is the sole workspace-pin authority
Fixes
- 5ab37ce fix(cli): a seeded shard follows the connection instead of refusing off a stale root
- 6791349 fix(cli): close the three identity forks the second round found
- eba5e89 fix(cli): close two identity forks the first pass left open
- f0aa0b3 fix(cli): conformance scenario pins its workspace; roots/list probe is bounded (#435)
- 371f830 fix(cli): defer the pre-Execute workspace-arg pin for calls that declare an identity
- 4f6179f fix(cli): fail an anonymous call on a shared connection closed to the connection
- cdc17bc fix(cli): keep
plumb stopinside its own environment, and unfreeze healed identity records - 90490b2 fix(cli): keep both arms of the identity write guard, and the legacy runtime dir
- 4f6bbd9 fix(cli): latch the shared-connection health mark; never clobber a specific note
- 93af24c fix(cli): make session identity survive a reconnect, and report it honestly
- 9ee21b8 fix(cli): re-assert the shared-connection mark after a Health-clearing re-pin
- 6fce1ea fix(cli): read the shard's root under sh.mu in followConnectionShards
- 6a16be0 fix(smoke): wait for the isolated daemon to exit before returning
- 7c49ea3 fix(tools): address review nits on the duplicated-subcommand refusal
- 59ae914 fix(tools): guard the LSP uri anchoring seam on a contested connection (issue #182)
- 97edb8e fix(tools): refuse a git call whose args repeat the subcommand
- 64ab5e7 fix(tui): persist single global settings sparsely instead of freezing defaults
- 5c2236f fix: read .gitignore in the workspace census and the topology index
- 424af6d fix: stop a gitignored tree deciding the workspace language
Others
- a2008c2 Extract gitignore matching into internal/ignore (Foundation)
- 8d6d10f Let a child .gitignore override a parent, as the walker claimed it could
- 8c3aa37 Make *.py.lock a weak marker: a locked helper is not a project root
- 42cbedd Name a language that can actually run, and pin the branch that says so
- cc0b633 Pin the language argument at the tool seam, and stop rendering "language ()"
- 20f9133 Reach a second language's tasks, and refuse an override that cannot be honoured
- 0ca9f7f Refuse a catch-all exclude by probing it, not by recognising its spelling
- c391d2c Stop a set judging its own directory, which hid re-included files
- ecd0851 chore(deps): bump charm.land/bubbles/v2 in the charm group (#438)
- 48569e1 chore(deps): bump charm.land/bubbletea/v2 in the charm group (#415)
- 59f2b2c chore(deps): bump modernc.org/sqlite in the minor-and-patch group (#417)
- dc748ad chore(deps): bump svelte and vite in the npm group, refresh generated UI assets (#433)
- 64cab44 chore(release): cut 0.18.0
- 3f2e174 ci(conformance): run the gate on every push to main (#437)
- 698ad12 ci(conformance): run the gate on the paths it asserts on
- 4fb419d ci(lint): pin golangci-lint to v2.13.1, matching the binary in use
- 835b187 ci(release): fail fast on a lapsed Homebrew tap token, and make it checkable
- 86ed695 ci(release): fail the release when the conformance gate is skipped
- 763d7ce docs(changelog): consolidate the duplicated 0.17.8 section headings
- 953dce1 docs: correct the layer seam claim and document run_command
- c54ca9f docs: slim agent brief and move reference detail
- 8328b69 site: five claims drift fixes — plumb tui, HTML tier, language count, tool groups, version stamp
- 3c94ead test(cli): guard the roots/list probe bound; split timeout from unsupported logs (#436)
- 967ad74 test(cli): pin the symlink canonicalisation on both sides of the ownership probe
- 38e2214 test(clientsmoke): run plumb setup inside the isolated HOME
Install
Homebrew (macOS + Linux):
brew install plumbkit/plumb/plumbDirect download: grab the archive for your OS/arch below, extract it, and
put the plumb binary on your PATH.
| Platform | Archive |
|---|---|
| macOS, Apple Silicon (M-series) | plumb_0.18.0_darwin_arm64.tar.gz |
| macOS, Intel | plumb_0.18.0_darwin_amd64.tar.gz |
| Linux, Intel/AMD 64-bit | plumb_0.18.0_linux_amd64.tar.gz |
| Linux, ARM 64-bit | plumb_0.18.0_linux_arm64.tar.gz |
Verify your download against checksums.txt.
macOS note: these binaries are not yet code-signed. On a direct download
Gatekeeper may block the first run — clear the quarantine flag once:
xattr -dr com.apple.quarantine ./plumb. Homebrew installs are unaffected.
v0.17.7
Changelog
Fixes
Others
- 83dcba7 release: 0.17.7
- 02cd333 test(cli): assert the contested mark survives the re-pin's own Health reset
Install
Homebrew (macOS + Linux):
brew install plumbkit/plumb/plumbDirect download: grab the archive for your OS/arch below, extract it, and
put the plumb binary on your PATH.
| Platform | Archive |
|---|---|
| macOS, Apple Silicon (M-series) | plumb_0.17.7_darwin_arm64.tar.gz |
| macOS, Intel | plumb_0.17.7_darwin_amd64.tar.gz |
| Linux, Intel/AMD 64-bit | plumb_0.17.7_linux_amd64.tar.gz |
| Linux, ARM 64-bit | plumb_0.17.7_linux_arm64.tar.gz |
Verify your download against checksums.txt.
macOS note: these binaries are not yet code-signed. On a direct download
Gatekeeper may block the first run — clear the quarantine flag once:
xattr -dr com.apple.quarantine ./plumb. Homebrew installs are unaffected.
v0.17.6
Changelog
Fixes
- 66bdc26 fix(cli): always announce the restart, and never report the stopped PID
Others
- 8a85bf9 chore(release): cut 0.17.6
Install
Homebrew (macOS + Linux):
brew install plumbkit/plumb/plumbDirect download: grab the archive for your OS/arch below, extract it, and
put the plumb binary on your PATH.
| Platform | Archive |
|---|---|
| macOS, Apple Silicon (M-series) | plumb_0.17.6_darwin_arm64.tar.gz |
| macOS, Intel | plumb_0.17.6_darwin_amd64.tar.gz |
| Linux, Intel/AMD 64-bit | plumb_0.17.6_linux_amd64.tar.gz |
| Linux, ARM 64-bit | plumb_0.17.6_linux_arm64.tar.gz |
Verify your download against checksums.txt.
macOS note: these binaries are not yet code-signed. On a direct download
Gatekeeper may block the first run — clear the quarantine flag once:
xattr -dr com.apple.quarantine ./plumb. Homebrew installs are unaffected.
v0.17.5
Changelog
Features
- bdfa538 feat(topology)!: record call sites and resolve Go cross-file call edges
- 43c9669 feat(topology): consume durable call edges (#419)
- d5b8b27 feat(topology): package-level reachability from entry points (PLAN-371)
Fixes
- 6618467 fix(cli): gate replayed session-ID adoption on the persisted proxy pairing (PLAN-296)
- 9f3bd63 fix(cli): make run_task's target remedy keep the caller's own command (PLAN-374 r1)
- a228cfd fix(cli): print Starting... and the daemon PID on plumb restart
- 0e5d5cc fix(cli): reconcile a stored task command that spells out its {target} default (PLAN-374)
- cc389fb fix(daemon): hot-reload project config to every attached session via a per-workspace watcher
- 27c37da fix(scripts): fail a CHANGELOG entry landing in a section released since the fork
- 0c00385 fix(scripts): report R4's pure-addition carve-out and stop the report contradicting itself
- d8ca710 fix(session): a refused per-agent re-pin commits nothing; the log is its trace
- f14b265 fix(session): commit session_start's linkage only on success; trace per-agent refusals
- 4158bf9 fix(session): retain external identity across session-ID adoption (PLAN-404)
- 31267c9 fix(session_start): resolve the caller's identity before the workspace re-pin
- 162fec3 fix(tools): call_hierarchy is the eighth PLAN-403 site — give its fallback a live context and headroom
- 304bd28 fix(tools): let read_symbol fall back when gopls is slow, not just broken (PLAN-390)
- fa4bd64 fix(tools): refuse an ambiguous move_symbol name on whichever tree answered (PLAN-403)
- 5e04fee fix(tools): run the symbol tools' tree-sitter fallback on a live context (PLAN-403)
- 6a0fae0 fix(tools): stop quoting a wait that never happened, and two guards that could not fire
- 3e5b83f fix(tools): tell the agent the server was SLOW, not absent, in the read-tool fallback banner (PLAN-403)
- 61187c7 fix(topology): HasGoSignal must not trust a bare KindImport node (PLAN-371)
- 9a9154a fix(topology): count intra-file call edges by source, not by circumstance
- 14eba42 fix(topology): exclude derived call edges by source, and make the call census reconcile
- d31e5c3 fix(topology): finish the ceiling split — the byte cap too, and say when a walk is cut (PLAN-407)
- cd66c6f fix(topology): gate Go-only refusal on a language signal, not edge count (PLAN-371)
- cf77989 fix(topology): make derived edges durable across re-index (#418)
- cf74d32 fix(topology): reachability review fixes — test-edge scoping, Go-only refusal, races (PLAN-371)
- f994b93 fix(topology): stop clamping an in-process traversal budget with the tool-argument ceiling (PLAN-407)
- a12eff8 fix: PLAN-414 review — watcher self-heals on .plumb swap, retry gate, teardown race
Others
- 8410b0d chore(changelog): re-file under 0.17.3; drop the now-dead isShared gate
- 6fd0b34 docs(changelog): disclose the no-topology trade-off in the PLAN-390 entry
- f97fc01 docs(changelog): name the corpus the call-graph census was measured on
- e79e9ec docs(session): the shardFor gate comment no longer names a removed symbol
- 511ad1e docs(topology): the inert byte ceiling was this PR's first round, not the original state
- 4c11834 refactor(config)!: remove the [commands] keys that gated execute_shell_command
- baa8b29 refactor(tools)!: retire execute_shell_command from the registered tool surface
- 31207c0 test(cli): close two guards mutation showed were uncovered
- dde7ec4 test(cli): drop the shell-refusal fixture along with the tool it pinned
- 3611933 test(cli): guard that a REFUSED agent never becomes the attach-time identity
- b30ae28 test(cli): guard that session_start is actually handed the declared-agent channel
- 409186b test(cli): make the drifter the second declared agent in TestMultiAgentPin
- 88efd95 test(cli): pin the per-agent refusal trace lifecycle, mark and heal
- 5b31f32 test(smoke): drive the PLAN-390 cold-server case deterministically over the wire
- c0a6810 test(smoke): drive the PLAN-403 write path against the never-answering server
- 58a7bc7 test(tools): give each slow-LSP subtest its own caller deadline, and tighten the warm bound
- a400be2 test(tools): guard the PLAN-403 fallback in both directions, and on disk
- f071c6a test(tools): guard the slow-server disclosure and the rounded budget it quotes
- c366095 test(tools): pin the advertised max_bytes ceiling at both tool call sites
- cce85eb test(tools): query workspace_symbols by prefix so the assertion cannot pass on the echo
- bb928d3 test(topology): fix unreachable-sort test that could not fail (PLAN-371)
- d802696 test(topology): isolate HasGoSignal's two clauses (PLAN-371)
- 5e392d8 test(topology): pin the package-dir language filter the census depends on
Install
Homebrew (macOS + Linux):
brew install plumbkit/plumb/plumbDirect download: grab the archive for your OS/arch below, extract it, and
put the plumb binary on your PATH.
| Platform | Archive |
|---|---|
| macOS, Apple Silicon (M-series) | plumb_0.17.5_darwin_arm64.tar.gz |
| macOS, Intel | plumb_0.17.5_darwin_amd64.tar.gz |
| Linux, Intel/AMD 64-bit | plumb_0.17.5_linux_amd64.tar.gz |
| Linux, ARM 64-bit | plumb_0.17.5_linux_arm64.tar.gz |
Verify your download against checksums.txt.
macOS note: these binaries are not yet code-signed. On a direct download
Gatekeeper may block the first run — clear the quarantine flag once:
xattr -dr com.apple.quarantine ./plumb. Homebrew installs are unaffected.
v0.17.2
Changelog
Features
- 4508b0f feat(cli): versioned, self-cleaning skills sync (PLAN-365)
- 90073ba feat(clientcaps): client-awareness — capability probe and progressive tool profiles (PLAN-369)
- 627aa68 feat(codex): install opt-in mailbox hooks
- 7efb9a8 feat(hooks): plumb hooks — install, status and uninstall, for Claude Code and Codex
- 467cfa2 feat(mcp): align initialize instructions with the managed brief (PLAN-366)
- 5fc273d feat(mcp): session_start brief mode — cheap orientation for subagents
- ebd67ae feat(setup): managed instruction block — the mechanism (PLAN-364 PR 1 of 2)
- c3fe18e feat(setup): per-client instruction templates, apply_patch countermand, --uninstall removal (PLAN-364 PR 2 of 2)
- dd8e620 feat(stats): three standing health metrics — make abandonment visible (PLAN-368)
- a587f45 feat(tasks): allow project-defined run_task slots
- b2966e3 feat(tasks): follow the workspace's package manager, and reach extra slots from the CLI
- 756eea6 feat(tools): fail_on_new_errors — a plumb edit can refuse to break the build (PLAN-362 PR2)
- 0f30d9a feat(tools): read_multiple_files header dedup (PLAN-357 commit 3)
- 0e16bdc feat(tools): read_multiple_files uniform slicing/search params (PLAN-357)
- fba012a feat(topology): resolve imports into cross-file edges; answer with packages
Fixes
- 8fff729 fix(argalias): stop suggesting a parameter's opposite, and carry the spellings agents send
- f99d122 fix(changelog): put the restored blank line back in its actual original spot
- df3048a fix(changelog): restore 2 blank lines eaten inside the released 0.17.1 section
- df2b9ae fix(cli): refuse stop/restart without a terminal instead of crashing in the TUI
- ef2fb23 fix(cli): skills sync — manifest is the ONLY proof of ownership (PLAN-365 review)
- d31b05f fix(clientcaps): PLAN-369 review round 1 — remove unsafe unknown→lean rung, tighten evidence bar
- 233744a fix(clientsmoke): Codex reports client-side-allowlist, not the generic reason
- 52b200f fix(codex): accept hooks install client argument
- 23726dc fix(codex): defer Plumb tools from direct surface
- 8dadcb0 fix(hooks): fold the hooks removal into main's uninstall side-effect helper
- 70eaa75 fix(hooks): review round 1 — an uninstall must never delete a hook plumb did not install
- 5348229 fix(hooks): review round 2 — three of these defects were in round 1's fixes
- 29fd110 fix(hooks): review round 3 — close the lock defect's third form, pin the guards nothing tested
- a8d9b0b fix(mcp): PLAN-358 review round 1 — valid nested example, exact-match priority, changelog attribution
- 5edb41e fix(mcp): PLAN-366 review round 1 — real topology-active test, restore .plumb-init recovery
- 07e8a56 fix(mcp): brief mode carries re-pin, LSP skip note, resumed name, mail
- e70ddee fix(mcp): pin the discovery ladder into Claude Code's context, tighten description budget
- 9206f5b fix(mcp): review round 1 — structural pin-superset test, restore ttl clause, fix stale byte count
- 87616f1 fix(minimal_diff_review): cap per file so a bundle cannot hide the rest
- 2917020 fix(minimal_diff_review,changelog): review round — per-file drop reason, capped list, honest antonym claim
- 41dbdae fix(read_multiple_files): drop the decorative rule and the wrong byte count
- b46e233 fix(setup): an unverified client layout reads as "not installed", not an error
- e3cfe61 fix(setup): keep a YAML client's own indentation instead of imposing 4 spaces
- 54c1479 fix(setup): precision nit — edit_file warns, only a guarded call (or write_file) refuses
- 23e3c38 fix(setup): review round 1 — dangling symlink stays a symlink, drop false CC-harness quotes
- b016180 fix(setup): review round 1 — refuse malformed/duplicate blocks, dedupe by real file
- c09a173 fix(stats): PLAN-368 review round 1 — fix lane-defection over/undercounting, semantic sample-size guard
- 986b36c fix(stats): PLAN-368 review round 2 — lane-defection was inverted by round 1's fix
- 9892eae fix(tasks,guidance): honour the pinned-tool rule and drop ambient detection from CLI tests
- 4c24687 fix(tasks,topology_search): trim descriptions back under the pinned-set budget
- 05186a7 fix(tools): PLAN-360 review round 1 — truthful path_prefix, doc comment, table row
- eceb665 fix(tools): PLAN-363 review round 2 — prove disambiguation candidates round-trip
- e871609 fix(tools): label post-write diagnostics fresh vs stale (PLAN-362 PR1)
- 926a14c fix(tools): lead with truncation notices; ship a read-only run_command allow-list
- 406e91d fix(tools): read_multiple_files tracker + wiring parity (PLAN-357)
- b6c26cc fix(tools): restore chars/baseline on read_multiple_files headers (review)
- 6370b31 fix(tools): review round 1 — commit the txlog before notify when the gate is off
- b8f7796 fix(tools): review round 1 — labelled timeout-with-nothing-cached, third pull-failure label
- e65299c fix(tools): symbol_name primary + snap-never-error for explain_symbol/type_hierarchy (PLAN-363)
- 6a69c77 fix(tools,cli,mcp): PLAN-361 review round 1 — falsifiable pin leg, both wiring axes
- 6db7455 fix(tools,mcp): edit_file rejection UX — every rejection is a one-call retry (PLAN-358)
- ab91f03 fix(topology): close two holes adversarial review found in the language-aware target
- d8a9ed6 fix(topology): derive the test target from the language, not from Go
- e52821d fix(topology): round-2 review — the fixture guard read the wrong 50
- 4869749 fix(topology): stop seeding affected-test traversal from import names
- 7568173 fix(topology): stop spending the package cap as a node budget
- aff469f fix: PLAN-367 review round 1 — net the CLI/TUI/web tables, fix surcharge ratio provenance
- 047e8bb fix: PLAN-367 review round 2 — net the web /api/stats bubble-scatter chart
Others
- a263098 Close out #9: $XDG_RUNTIME_DIR for the daemon socket, Kotlin validated on Linux (9/9) (#381)
- 87163af Linux hardening (#9): Wayland clipboard, XDG paths, adapter tier drift (#380)
- f4ed038 build(lint): size the lint retry budget for a queue, not for one peer
- db59c75 chore(deps): gotreesitter v0.48.1 -> v0.51.0; retire the C enum workaround
- cf92c45 docs(changelog): PLAN-361 wording fix — pin test guarantee (review)
- 382d35d docs(changelog): PLAN-363 — describe the shipped disambiguation behavior accurately
- 832e25e docs(changelog): PLAN-376 re-home entry under a fresh 0.17.2 (unreleased) heading
- dc184fd docs(changelog): re-home PLAN-368 entry under 0.17.2 (unreleased)
- 8523f23 docs(changelog): relocate rebase-orphaned entries to 0.17.2 (unreleased)
- cc4cded docs(cli): skills sync — correct two misleading strings (PLAN-365 review)
- df10c80 docs(skills): PLAN-376 refresh shipped skill content for wave-1/2 doctrine
- 2078713 docs(skills): PLAN-376 review round 1 — transaction_apply diagnostics, autoBrief truthfulness
- b098f77 docs(tools): topology_routes truth-in-labeling (PLAN-360, worth-it W1-6)
- fc9297e docs(topology): lead with the narrowing knobs in explore and search
- a9b836a docs(topology): max_results describes packages, which is what it now bounds
- e1e19e4 docs(use-cases): measure against a real read tool, and re-measure after the fixes
- 6f183f4 docs(use-cases): pin the re-measured scenarios to their commit
- 968c9ed docs(use-cases): re-measure every number, and add a script that regenerates them
- ebb7b2d...
v0.17.0
Changelog
Features
- 9a16792 feat(cli)!: remove the Antigravity legacy flat-config layer
- 88debe0 feat(cli): add
plumb setup kimi-workfor the Kimi Work desktop app - c13ccc7 feat(cli): doctor MCP Clients alphabetical, with diagnostics as branches
- 48684b5 feat(cli): doctor moves live LSP sessions to their own section; capability-trust row realigned
- add2db9 feat(cli): grouped-table renderer, shared status colours, terminal-accurate TTY checks
- 0f8346a feat(cli): interactive client picker for bare plumb setup
- 70615a5 feat(cli): plumb mail, so a hook can ask whether an agent has mail
- 1e5c289 feat(cli): plumb serve --workspace flag pins the attach hint
- 6b62ef4 feat(cli): plumb setup --uninstall
- b33ecc0 feat(cli): plumb skills groups the status table per client with status colours
- 9bc4c33 feat(cli): plumb skills sync reports through the grouped per-client table
- da9536b feat(cli): plumb trust joins the shared CLI presentation, confirmed by the Yes/No selector
- 130b6f0 feat(cli): sessions --json machine-readable mode (PLAN-336)
- 5670c86 feat(cli): skills sync summary section, unregistered rows, doctor branch wrapping, trust record
- e14ae5a feat(collab): count what the mailbox is doing, instead of capping it
- 4c911ad feat(collab): let a restarted session inherit its predecessor's mailbox identity
- 3c48f10 feat(collab): tell a sender which of its messages nobody has read
- 165677e feat(git): report a plumb-caused git timeout as plumb's, and make the bound configurable
- 90f32fc feat(mcp): carry the per-call logical agent in the tools/call ctx
- ecdb618 feat(scripts): catch a CHANGELOG entry added under the wrong heading
- 2a7a2be feat(scripts): guard against a duplicate CHANGELOG version heading
- 3ea1249 feat(session): adopt the replayed stable session ID (PLAN-296)
- 39b8421 feat(session): capture the logical-agent identity from tools/call _meta
- 8fbf254 feat(session): consume per-agent trackers/rate/undo/pins in the live tool path (PLAN-286)
- 74ff9c2 feat(session): daemon reads the replayed stable session ID (PLAN-296 round-trip)
- ffed479 feat(session): fail closed on anonymous state-changing calls over a shared connection
- bb709c3 feat(session): key pin/trackers/undo/rate/language per logical agent (PLAN-286 step 2b-c)
- a992a32 feat(session): reconcile mailbox inheritance with the adopted ID (PLAN-286 step 4)
- 0e556c5 feat(session): serve proxy holds and replays the stable plumb session ID
- b67b147 feat(sessionstate): persist per-agent reads and pins (schema v6, PLAN-286 step 2e)
- 56bfe70 feat(setup): add DeepSeek Harness as a setup client
- f01b70a feat(setup): add Junie as a setup client
- 469992f feat(setup): add ZCode as a setup client
- 11a651c feat(tasks): run commands where the module is, and make scoping reachable
- 0153ba7 feat(tools): mutation_test — explicit-mutant mutation testing with a mandatory compile gate
- 15e6a26 feat(tools): resolve per-agent workspace/boundary via request ctx (PLAN-286 step 2d)
- a86237e feat(tools): session_start flags an unlinked session (PLAN-337)
- 7b38d4d feat(tools): surface task state, batch deletes, and lift the context_lines cap
- 15042b8 feat(topology): surface skipped-file reasons in status, web API and doctor
- 7e8d1ea feat(web,tui): show daemon-wide conversation volume when every participant opted in
Fixes
- 688b448 fix(attach): bind the claim message by its remedy; CUT the dashboard change
- bcd5ada fix(attach): state the refusal's real cost, and pin it with a test
- 8a4b44e fix(attach): stop the alarm accusing a caller of forgery the daemon disarmed
- 9dfbf3f fix(attach): surface a forged wide claim, and sweep pre-fix forged pins once
- 641de94 fix(attach): the replayed _meta pin no longer carries the home exemption
- 91dd499 fix(attach): withhold the home exemption from the replayed pin on policy rebuilds too
- af18679 fix(build): retry golangci-lint on the shared-cache lock
- cebc18a fix(ci,test): let a failing test name itself, and stop asserting wall clock
- ca53da3 fix(cli): apply the saved [ui] theme to the whole CLI, not just the dashboard
- 3d9ad36 fix(cli): cap the setup table's Config column at 60 columns
- 35f9bae fix(cli): close sticky-pin residuals and PLAN-300 follow-ups
- 391db75 fix(cli): do not start the Xcode flow from a config that failed to load
- 0564926 fix(cli): make --workspace resolve a workspace, and stop overclaiming the hook
- 5268fde fix(cli): name why no LSP is attached for a home-directory pin
- 67851f3 fix(cli): print setup --all errors below the table, not in its Config column
- 4292423 fix(cli): refuse a restore whose re-resolution drifts from the verified root
- 84f2009 fix(cli): refuse undeclared roots that CONTAIN a home directory
- b1e7717 fix(cli): space key was dead in the setup picker and the Yes/No selector
- 2372688 fix(collab): a conversation id addresses a thread, it does not grant one
- 3fa005e fix(collab): address a message to a session, not to a name
- f167e7a fix(collab): close the second name-shadowing lane, in workspace_sessions
- bb09d71 fix(collab): count claimable "next" notes in the plumb mail wake probe (PLAN-343)
- 8794aa0 fix(collab): count only the sender's bytes in the truncation report
- c39d584 fix(collab): merge the receipt across both stores, and stop overstating it
- 774a575 fix(collab): never hand a note back to the session that wrote it
- ddce59b fix(collab): refuse a cross-project leave_note the recipient will never read
- 839d027 fix(collab): refuse a thread whose other participant could not be placed
- 61b1dba fix(collab): refuse an unregistered sender, and stop hiding the wait
- 4edfbbd fix(collab): review follow-through on the cross-project consent refusal
- 53f5ea3 fix(collab): stop hiding truncated notes, and surface the mailbox policy up front
- 2508049 fix(collab): the unregistered-session guard tested the wrong field
- 702a708 fix(config): a config plumb cannot read means no config, not the last one
- f3010b1 fix(config): collapse fold variants in document order; refuse to clobber non-table segments
- d0b125d fix(config): do not fold map keys when collapsing fold variants
- 9107022 fix(config): do not reuse a fold-variant key that holds an array of tables
- 2c602f0 fix(config): fold key case in the project-config sparse read/write/unset trio
- 18c9a40 fix(config): fold key case the way go-toml does, not the way EqualFold does
- ab3ab39 fix(config): fold the two remaining raw-map walkers, and make the fold deterministic
- 70c5a11 fix(config): match the decoder's ToLower fold in the project policy spec
- 343d1b8 fix(config): show a provenance row for every [git] field, and guard the drift
- 3f423de fix(delete_file): dedup batch paths by canonical key, not raw string
- 28dfbed fix(deps): bump nanoid to 3.3.18 (GHSA-2v37-7h3g-55p8)
- 87ac195 fix(detect): break contested root markers by what the project contains
- 293459b fix(detect): tie-break scan budget starved by a large resources tree (PLAN-340)
- abd603a fix(edit_apply): narrow the bare-mention carve-out, stop aliasing the caller's edits
- d0f3918 fix(edit_apply): refuse a WorkspaceEdit naming one ...
v0.16.6
Workspace-root hardening. Several ways the workspace root could end up wider than anything you chose — up to and including your entire home directory — are closed. If you use [workspace] auto_attach, or plumb has ever attached to a directory you did not explicitly pin, upgrade.
Full detail for every item is in CHANGELOG.md — this is the readable summary.
Security
-
Your home directory can no longer become the workspace root by accident. A dotfiles repository checked out at
$HOMEis a common setup, and the synthetic-root fallback walked up to the nearest.gitwith no guard — so a session seeded anywhere beneath it resolved the workspace to$HOMEitself. The whole home directory then became a single read-write root, putting every SSH key, browser profile and credential file inside the boundary, with nothing in the tool call to say the workspace had widened. Four routes led there and all are closed: a dotfiles repo, an incidental tool path (oneread_fileof~/.zshrc), a persisted pin replayed on reconnect, and a~/.plumbleft behind by an earlier build'sauto_attach_persist.Two details worth knowing.
$HOMEis matched by filesystem identity across both the$HOMEvariable and the OS user database, so a symlinked spelling cannot slip past and a client that empties or repointsHOME— the daemon inherits its spawner's environment — cannot disarm the guard. And "you asked for this" now means the seed is yourworkspaceargument: a call carrying both a path and a workspace (relevant_memories,write_memory) no longer lets an incidental path be treated as a deliberate declaration.If an earlier build left a
~/.plumbbehind:touch ~/.plumb/context.mdkeeps that directory's memories and topology index and silences the warning, or remove~/.plumbto discard it — which deletes those too.auto_attach_persistcan no longer create one there.Known limit, stated rather than left to be discovered: a root that contains home directories —
/Users,/home,/, or/System/Volumes/Dataon macOS — is not guarded. Three implementations were built and all three were defeated by macOS path aliasing (/System/Volumes/Data/Usersis/Users, and compares unequal under every resolve-then-compare test). Shipping a guard the platform walks straight past would be worse than not claiming one, so it is tracked as #306 with the full evidence. -
A deleted pinned workspace no longer rehydrates to the enclosing repository. A removed git worktree is the standing case: the persisted pin was restored by walking up to the nearest thing that looked like a project, silently widening the session's write surface past anything you chose.
Fixed
-
A relative workspace root can no longer brick a session. Detection resolved a relative root against the daemon's working directory while the boundary policy declined to, so the two halves disagreed:
session_start({workspace: "."})succeeded and then refused every path — including the workspace's own files — with an error naming the workspace as.and advising the re-pin that had just happened. Six routes can set the pinned root; the invariant now lives where all six converge. Pass an absolute path:.means your directory, which is not the one a shared daemon resolves. -
The two functions answering "is this the same place?" have become one (#273). They disagreed on exactly the cases each was written for — one anchored a relative path to the daemon's working directory, and cleaned
..lexically before resolving symlinks, which is how a check and a syscall end up naming different files. A newinternal/archrule now fails a test if path canonicalisation is ever reimplemented, rather than waiting for a reviewer to notice. The sweep that rule forced found five copies, not the two the issue named — including one that started two concurrentxcodebuildflows for a single project reached by two spellings. -
Also: two agents on one project reached by different path spellings no longer disagree about where they are; a relative root in your global config now grants nothing rather than granting access relative to the daemon's working directory.
Added
- C#, Elixir and Scala are indexed by the topology Map, completing the language-coverage programme — every language in the registry now has an extractor.
cherry-pickis admitted to thegittool's destructive tier; the MCP protocol version is negotiated at initialize.
Install: brew install plumbkit/plumb/plumb, or grab a binary below (macOS and Linux, arm64 and amd64). Existing Homebrew users: brew update && brew upgrade plumb.
v0.16.5
Three attach-triggered vulnerabilities, all live in 0.16.4 and all reachable from a hostile clone. If you use plumb on repositories you did not write, upgrade.
Full detail for every item is in CHANGELOG.md — this is the readable summary.
Security
All three are triggered by attaching a session to a repository. No tool call is needed, and nothing in the repository looks unusual — the payload is a committed symlink or a committed file.
- A cloned repository could delete directories anywhere you can write.
transaction_applykeeps a write-ahead log under<workspace>/.plumb/tx-log/, andtxlog.Scanwalks it on every attach and re-pin to roll back orphans. That path is an ordinary directory inside the workspace, so a repository can ship it — as a symlink. Scanning followed it out of the workspace and cleaned up what it found there. No manifest was required. - A cloned repository could write a file anywhere you can write, with mode bits of its choosing. Same scan, via a committed orphan manifest: the rollback wrote the manifest's recorded content to the manifest's recorded path, neither of which was confined to the workspace.
- A committed symlink plus
..defeated the workspace boundary outright. Every boundary check ranfilepath.Absfirst, which cancelssub/..lexically before any link is resolved; the kernel does the opposite, followingsuband then applying..to wherever that landed. Whensubis a symlink out of the workspace the two readings name different files, and the syscall received the same uncleaned text the check had approved. Measured against the real tools:write_filewrote outside every allowed root,read_filedisclosed the target,find_fileslisted it. One committedsub -> /made the whole filesystem addressable as in-workspace. Such a path is now refused rather than cleaned — cleaning it would keep every call working while silently retargeting it to a different file than the caller named. - Four more project-config fields that could soften a global safety setting are now covered by the trust gate.
Changed
- Kotlin moves to JetBrains'
kotlin-lspand is promoted out of experimental. Note the macOS quarantine step indocs/adding-an-lsp.md— macOS silently deletes the unsigned server binary on first run unless it is cleared. [collab]'s channel switches are gated onplumb trustinstead of being forced global-only. 0.16.4 closed a real hole by making them global-only, but that also removed per-workspace chat settings entirely. Trust separates the two questions: settings can vary per workspace, while the user — not the repository — decides whether this repository's settings are honoured.- Per-root language-server state directories are a table rather than a special case.
Fixed
- Two live sessions can no longer share a name, so a mailbox address always reaches the session it names. A name is an address: notes are stored and notified by the name string, so a duplicate silently misdelivered.
- A tool added by a daemon rebuild is no longer invisible to already-connected clients. The tool list is fetched once at connect, and
plumb serveis a reconnecting proxy — so from the client's side the server never goes away and never re-lists. The proxy now emitsnotifications/tools/list_changedafter a reconnect. This is the case plumb's own dev loop hits constantly: rebuild the daemon under a live agent session and the new tool was unusable until the client restarted.
Install: brew install plumbkit/plumb/plumb, or grab a binary below (macOS and Linux, arm64 and amd64). Existing Homebrew users: brew update && brew upgrade plumb.
v0.16.4
Agent-to-agent messaging, a trust gate that covers the settings that can run code, structured failure classification, and build provenance.
Full detail for every item is in CHANGELOG.md — this is the readable summary.
Security
[collab]'s channel switches are forced to the user's global value. A project's.plumb/config.tomlis an untrusted surface — a cloned repository ships one — and[collab]was missing from the list of sections a repo may not set for itself, so a clone could openintents,mailbox,cross_projectorknowledge_handoffon its own. (Already superseded onmain, shipping in the next release: these are gated onplumb trustinstead, so per-workspace chat settings work again — with the user, not the repository, holding the switch.)- A project's
.plumb/config.tomlcould run arbitrary code, and could open settings it should not.plumb trustnow covers a project's[lsp.<lang>]and[git]settings — the ones that decide what gets executed — rather than only the obvious exec fields. - A fold-variant key escaped the trust gate entirely. go-toml folds a table name to a struct field the same way it folds a field name, so a case-variant
[lsp.<lang>]decoded into the config while an exact lookup left it out of the trust spec. Matching is now case-insensitive. plumb trustnow asks. It previously printed the disclosure and granted in the same breath.protected_brancheswarned only on an empty list, though the list is the complete set — a shortened one is the more likely mistake.- A published threat model:
docs/threat-model.mdnames plumb's assets, trust boundaries and non-goals.
Added
- The mailbox is a real agent-to-agent conversation channel.
leave_note+check_messages: messages ride on any tool result so you receive them while working,check_messages({wait_seconds})blocks server-side so you can hand your turn to a peer instead of polling, replies thread byconversation_id, and a thread is capped by an exchange budget. Same-workspace by default; cross-project delivery is the recipient's opt-in. - Tool failures carry a stable, machine-readable classification. Write, boundary, rate-limit, language-server and git refusals are all classified, a failing git child carries its own kind, and a failed
tools/callcarries the classification on the wire. Classification happens exactly once per call, at the MCP dispatch boundary. plumb setup codex --lean/--leanfor Gemini CLI write a client-side tool allowlist, andplumb doctorgrades every one of them.- A project config plumb is ignoring now says so, in four places, rather than being silently skipped.
Changed
- Build provenance. A binary can say which source commit it was built from:
plumb version --jsonprints a machine-readable report anddaemon_infogained asource commit:row. Both stampers measure source-tree dirtiness instead of asserting it, a revision that is not a plausible SHA is treated as no stamp, and "dirty state unknown" no longer renders as clean. plumb stats --failures(a breakdown by kind) andplumb stats --since <age>(scoping the whole command), with a "Failures by Kind" widget on the TUI dashboard. Stats schemauser_version16.- The TUI settings editor exposes the
[git]tier rows and the exec-deciding fields. - The
plumb-diagnoseskill teaches the structured lane first.
Fixed
- Doc comments in the topology index. Zig symbols carry their
///span; a//!container doc no longer attaches to the following declaration; Python methods in a class body and every decorated declaration carry theirs; and a comment trailing a body's header line is no longer collected as the first doc line. - Several
plumb doctorcorrections: no longer suggesting a repoint that would overwrite a client-side allowlist, naming a mistyped Codex allowlist correctly, not claiming a backup that may not exist, and a removeKey canary that was a no-op on Codex. session_startno longer steers a--leanclient at tools it cannot call.
Install: brew install plumbkit/plumb/plumb, or grab a binary below (macOS and Linux, arm64 and amd64).