# Changelog

## 0.14.1

### Patch Changes

- c47c68e: Refuse identity and coverage reuse of running Cargo work after workspace source changes, including same-size edits with restored mtime. Unknown source snapshots fail closed while unchanged sources and queued work retain sharing. Report active GitHub worker stages as in progress with refreshed elapsed time instead of cancelled with zero duration.
- 5637be2: Verify an active exact pool job when GitHub reports its aggregate matrix workflow as pending on a sibling lane's concurrency. Preserve repository, default branch, workflow, attempt, unique job identity and live job checks, so valid workers can claim admitted work without weakening provenance validation.

## 0.14.0

### Minor Changes

- 464137e: The GitHub Action runs more than one worker per lane. Set the new `worker` input
  (1 to 100) and name the job `Hauler pool / <lane> / <worker>` with a
  `hauler-ci-<lane>-<worker>` concurrency group. Ownership records and verifies the
  worker index, planning counts every live worker's remaining capacity, and workers
  that race for one head agree on a single holder that finishes it. Jobs
  without `worker` keep the `Hauler pool / <lane>` name.
- 501d93f: GitHub Action throughput: route mode delegates a trusted head that leaves policy untouched at once instead of waiting for lane checks, and the `route-wait-minutes` input is removed (drain still verifies checks before admission; run `mode: enqueue` on a schedule to recover missed enqueues). Drain workers are resident: a new `idle-polls` input (default 5, 0 to 60) makes a worker rescan after an empty admission scan, one poll interval apart, before exiting with its warm sandbox. A snapshot superseded by a new PR head now keeps the warm sandbox for the next snapshot.

## 0.13.7

### Patch Changes

- ebcd405: Name up to 10 failing JUnit test cases in the completed Hauler check-run summary and evidence JSON.

## 0.13.6

### Patch Changes

- 596adfa: Route wait tests no longer depend on timer precision.

## 0.13.5

### Patch Changes

- a8d5796: Route mode can outwait a late manager enqueue run. With `route-wait-minutes` set, route keeps waiting while the manager's `pull_request_target` run for the head is still queued or running, so a saturated runner pool no longer locks the head to native CI. Delegation still requires manager-owned checks.

## 0.13.4

### Patch Changes

- 6166659: The GitHub Action admission scan reads open pull requests, their check runs and admission receipts through paginated GraphQL queries instead of per-PR REST calls, so a scan costs a constant number of requests and no longer exhausts the `GITHUB_TOKEN` REST quota.

## 0.13.3

### Patch Changes

- 9087649: Spend less of the GitHub API budget in the CI action. Repeated GETs revalidate with their ETag so unchanged answers come back as free 304s, rate-limited requests wait for the reset or retry-after and retry instead of failing the job, and admission skips the per-PR read for pull requests whose list row shows no Hauler work.
- 1f9e1b8: The Hauler CI GitHub Action now prints the failing error, its stack, and the GitHub rate-limit headers of a rejected request when the controller fails, instead of only the generic failure line.

## 0.13.2

### Patch Changes

- b1d4794: The `tool/before` hook no longer loads its rendered view for shell commands without `cargo`, so `hauler await` and `hauler result` calls skip about 450 ms of hook time.
- 2d2e077: The kache status refresh now scans kache's index table in order instead of walking its crate_name index. On a 275k entry index the refresh query drops from about 5.4 s to 0.84 s of CPU.

## 0.13.1

### Patch Changes

- 644758c: The `tool/after` hook now records telemetry and looks up finished tickets in the cheap handler. It loads the rendered view only when there is additional context to inject, so a cargo command with nothing to announce skips the Flight worker.
- e8e3eb3: `cargo nextest --profile` no longer keys the compile lane. The lane follows `--cargo-profile` and `--release`. Nextest's own profile stays in request identity so `ci` and `default` do not coalesce.

## 0.13.0

### Minor Changes

- be97d27: Lanes now follow the directory cargo locks: one lane per workspace root, target dir, and profile output dir, so `cargo build` and `cargo build --profile perf` run at once while `build` and `check` still queue. A whole-target `cargo clean` waits for every build on its target dir, and builds that arrive meanwhile wait for it with a `target-clean` admission hold.
- f88b56d: The metrics window's queue-wait split now reports `prerequisiteBoundMs`, the wait a leader spent behind unfinished `--after` prerequisites, which "other" used to absorb. The dashboard shows it as a prerequisite-bound part.

## 0.12.2

### Patch Changes

- c797e9e: A `cargo test` run with two positional test names no longer leads, joins, or rides a shared run. Cargo rejects that invocation, and valid runs folded onto it used to inherit its usage error as a false failure.
- f358263: An identical request folded onto a queued run now attaches as an identity rider instead of a batch rider. Its saved compute is credited in full, so the dashboard and savings metrics no longer under-count it.
- 33bd8f8: A kache index scan that times out once no longer sticks. The next status refresh scans the index again instead of reporting it as timed out until the index changes or the daemon restarts.
- b33e3b5: The default admission permit count is now one per six cores instead of one per eight, still clamped to 5 through 16. A 96-core machine admits 16 leaders instead of 12. Replaying a week of ledger traffic from such a machine cut summed queue wait from 1,340 h to 571 h and p90 wait from 1,264 s to 695 s. `CARGO_HAULER_MAX_CONCURRENT` still overrides the default.
- d3c9430: A request that attaches to a running build and finishes before its attach is fully recorded now keeps the build's error and warning counts and diagnostics in its result.
- 088eacf: Install and doctor now skip a non-executable `cargo` file earlier on PATH, as the shell does. The installed shim is no longer reported as shadowed, and doctor finds a stale shim behind it.

## 0.12.1

### Patch Changes

- e779145: GitHub workers again copy each snapshot's exported test reports into the evidence artifact, next to its count summary, so a failed lane names its failing tests.

## 0.12.0

### Minor Changes

- c8fb90c: Keep scoped agent status small, apply status filters before ledger limits, and reselect queued work when admission becomes available. GitHub workers reuse verified live capacity, accept digest-pinned toolchain images, and publish bounded timing and test summaries as each snapshot finishes.

## 0.11.2

### Patch Changes

- 893a3c4: Honor native CI ownership across policy changes and plan queued prior-policy checks for retirement in serialized drains. Recheck ownership before cancellation; delegation still requires the exact policy.
- d09af46: Add read-only CI planning that starts workers only for lanes with eligible tests or queued-check maintenance. Drains share admission logic and revalidate the advisory plan under their lane lock.

## 0.11.1

### Patch Changes

- 62efb13: Prevent admission for PRs with confirmed merge conflicts and cancel their owned queued checks without interrupting admitted snapshots.
- eb2d4be: Keep native CI responsible for pull requests that change workflow, recipe, or worker image inputs, including renames and incomplete change lists.
- c95c170: Preserve timestamps for unchanged Git subtrees between managed CI snapshots so Cargo directory watchers do not rebuild solely because checkout directories are fresh. Changed and reverted subtrees retain fresh timestamps.

## 0.11.0

### Minor Changes

- 6cc7629: Add a GitHub Action that drains ready PR snapshots through isolated warm workers, reports each PR independently, and keeps GitHub credentials outside PR execution.

### Patch Changes

- 511cf1e: Keep GitHub Action admission provenance in check metadata so GitHub's rewritten check links do not force every PR back to native CI.
- a637ebd: Validate CI delegation through trusted policy and queued checks before native jobs hand work to warm workers. Preserve native fallback, retry infrastructure failures, and report safe worker progress.

## 0.10.0

### Minor Changes

- 177616d: Make await return compact state and blockers instead of full logs and internal scheduling metadata. The CLI and MCP await default is now 60 seconds; use result for output and diagnostics. Status no longer repeats active tickets in recent rows.

### Patch Changes

- 0d4c30e: Update agent-bundle to 0.3.1 (899755dc6d) so generated executables no longer ship bundled dependencies' comments.

## 0.9.14

### Patch Changes

- 6d587c8: Bump `agent-bundle` and `@agent-bundle/runtime` to the pkg.pr.new preview at `4f62216f30` (agent-bundle 0.3.0, `@agent-bundle/runtime` 0.2.0) and rebuild the committed plugin artifact.
- 1702562: Ticket ids must now be written exactly as hauler prints them, like `cc-1`. A zero-padded id such as `cc-01` is rejected as unknown instead of queueing an `--after` dependent that never wakes or making `await` sleep its full wait.
- 13be92e: `hauler --help` now lists the routed `kill` and `web` commands. The await timeout notice and the pending-ticket guidance now say a plain await waits 30s and name the option that raises it up to 2h (`--max-wait-ms` on the CLI, `maxWaitMs` for the MCP tool).
- 7412c9d: Effect modules are imported by subpath, so `hauler` run from TypeScript source no longer loads every `@effect/platform-node` module at startup, and a lint rejects Effect package-root imports. The bundled binaries are unchanged.
- 5e7c131: A foreground `hauler exec` that loses its daemon during `hauler daemon restart` now names what failed while it reconnects, for example that the daemon closed the connection before answering its readiness ping. It no longer prints `daemon startup failed: ConnectionClosed: ` with an empty message.
- e9e60d6: A lane head now folds compatible queued requests when it wins its admission permit instead of when the lane first takes it. Under a saturated permit pool the head could wait half an hour or more for a permit while a sibling `cargo test -p other --lib -- filter` or `cargo check -p other` submitted a second later sat behind it, then waited out a permit of its own; those requests now ride the head's composite run. The fold rules themselves are unchanged.
- e9e60d6: Batch folding now accepts `--locked`, `--frozen`, and `--offline` when every participant passes the same ones. These flags only assert things about the lockfile and network access for the whole invocation, but until now any of them kept a request out of every compile batch and test composite. In the last day a quarter of brokered `build`/`check`/`clippy`/`test` leaders carried `--locked`. A one-sided flag still refuses the fold, because the composite runs with the leader's flags.
- 244156c: The shell hook's "cargo ran outside cargo-hauler" notice matches cargo's twelve-column status alignment exactly. Indented prose such as `    Running the kernel constructor` no longer reads as a hidden cargo run, and a wrapped `cargo doc` (` Documenting foo`) now does.
- af91638: `hauler kill` with no daemon running now exits 0 and reports `nothing to kill (the daemon is stopped)` with the ticket's ledger record, like `result` and `await`, instead of failing with `[render-failed]`. The kill result now carries a `daemon` field.
- e3e9eec: `hauler exec` and `hauler request` now refuse a program that is not cargo, such as `hauler exec -- ls -la`, with `program must be cargo, got ls` and exit code 2. Before, the daemon or the local passthrough ran it and reported it as a cargo run.
- 8d3dd0a: `hauler daemon restart` against a daemon that accepts the connection but never answers now reports that its running state is unknown and that it was not restarted. It no longer claims no daemon was serving or tries to start a second one.
- 89174bd: `hauler status`, `log`, `last`, and the dashboard now lead with why an unresponsive daemon could not be read, such as `socket could not be opened (EACCES)`, instead of claiming the daemon is up but slow.
- 0e13603: CLI help, MCP tool descriptions, rendered documents, hook notices, and daemon and client messages now use whole sentences instead of semicolons, dashes, and arrows. For example, `hauler daemon restart` prints `restarted from pid 41 (0.4.1) to pid 42 (0.4.4)`, a passthrough prints `running cargo directly: daemon unreachable`, and a finished-ticket notice ends with `Call hauler_result cc-42.`. Status values, exit codes, ticket ids, `prefix: detail` error shapes, and field labels are unchanged.
- 73dca61: A ticket that ended before cargo started (killed while queued, a failed prerequisite, or daemon shutdown) no longer reports "0 errors, 0 warnings". Its result now shows no diagnostic counts because no cargo ran.

## 0.9.13

### Patch Changes

- da39c2e: The auto-background notice for a caller whose stdout is redirected no longer sends them to `hauler result cc-N --full`, which shows only the last 768 KiB of a larger log. It now says `hauler result cc-N` names the ticket's full log once the run starts; the ticket is still queued when the notice prints, so its log does not exist yet.

## 0.9.12

### Patch Changes

- 950914f: The daemon no longer freezes when a client stops reading a large build. Once a write has waited 2 seconds on such a client, the daemon cuts that connection's queued output to 1 MiB. With tens of MiB queued, the cut used to evict one message at a time and rebuild the truncation notice on each eviction, which held the daemon's event loop for about 170 ms. Every other client, hook, and status call waited that long. The cut is now one pass, and the notice text is rendered once when it is written. A second connection's ping measured 164 to 172 ms at worst during the cut before and 15 to 27 ms after.
- 2bd942e: The notice a client gets when it falls behind now names the ticket's log file: `output truncated: client fell behind; N bytes dropped; full log: <stateDir>/tickets/cc-N.log`. It used to say `full output: hauler result cc-N --full`, but `--full` renders at most the last 768 KiB of the log, so the dropped output was often not there. A rider's notice names its leader's log. When no log is kept, the notice points at `hauler result cc-N`. `hauler result` and the `--full` help now say that `--full` shows the last 768 KiB of a larger log.

## 0.9.11

### Patch Changes

- e44748e: A foreground `hauler exec` now receives every line of a long build. A `cargo build` with a few hundred or more output lines used to lose a run of lines, with `output truncated for slow client`, even when the reader kept up. The daemon capped each connection at 128 queued messages, and a burst of demuxed lines filled that cap before the connection's writer got its next turn. The daemon now keeps output for a client that is still accepting writes, up to 64 MiB of queued messages per connection. Once a write has waited 2 seconds on a client that accepts nothing, further output is shed down to 1 MiB of queue. The notice reads `output truncated: client fell behind; N bytes dropped; full output: hauler result cc-N --full`, and the whole run stays in the ticket log.

## 0.9.10

### Patch Changes

- 090ff2a: A background process that outlives cargo no longer keeps the daemon from stopping. A test that spawns `sleep 30 &` or a build script that forks used to leave the daemon holding that ticket's output pipes until the process exited, so `hauler daemon stop` reported the daemon as still running and restarts stalled. Once cargo exits and its short output drain ends, the daemon closes the ticket's pipes. The background process keeps running, and a write it makes to the closed output fails as it would for any closed pipe. The same applies to `hauler exec` runs without a daemon.

## 0.9.9

### Patch Changes

- 6d793d4: A running daemon from another release whose status report this client cannot read no longer makes `hauler status`, `hauler daemon status`, `hauler log`, `hauler last`, the dashboard, or the MCP tools print a raw schema error or a stack trace. They render the new `skewed` daemon state: the daemon's pid and version, whether it is older, newer, or another build, the command that replaces it, and its tickets from the ledger with their recorded status. `hauler result` and `hauler await` name a ticket record they cannot read instead of dumping the schema error.
  
  Reading the ledger without a daemon report (a stopped, unresponsive, or skewed daemon) no longer crashes with `no such column` when the ledger predates a column migration. The read falls back to the writable opener, which adds the missing columns.
- d10889b: Build against Effect `4.0.0-rc.117` and agent-bundle main (`1a77058`). The daemon and its clients read the socket through Effect's new pull-based reader. Requests, streaming output, reattach, kill, and shutdown behave as before. The bundle carries one Effect copy, so the CLI stays near its previous size.

## 0.9.8

### Patch Changes

- 1c3691f: Build against agent-bundle main (`1d661b4`). The `hauler_*` MCP tools are `defineTool` definitions, which agent-bundle now requires. Tool names, inputs, results, and `hauler` CLI commands are unchanged. Built artifacts no longer embed the checkout path, so the same commit builds to the same bytes in any directory.
- 1ba9a1d: The daemon scans the kache index in a worker thread, so a large index no longer stalls `hauler status`, ticket reads, or pings while it refreshes. A 4.5 GB index held the socket for about 590 ms per refresh; it now answers within a few milliseconds. The kache section names why the index could not be read (`not detected`, `index unreadable`, or `index read timed out` after 30 s) instead of reporting every failure as not detected.

## 0.9.7

### Patch Changes

- f0873d1: Status and the dashboard no longer count a batch rider's solo estimate as compute avoided. A batch rider's packages compile inside the leader's combined run, so it now credits no avoided compute. Its latency saved now counts its solo run from after the leader's compile estimate, because it was queued behind the leader until the leader handed the lane on. A batch that beats both runs back to back no longer shows as "finished later than alone". Rows written before this release keep their old credit.
- b3b6f4f: A live daemon whose socket this client cannot open (`EACCES`, `EMFILE`, `EPERM`) no longer reads as stopped. `hauler result` and `hauler await` fail with `render-failed` instead of calling a running ticket `orphaned` and `stranded by a stopped daemon`. `hauler status`, `hauler log`, and `hauler last` report the daemon `unresponsive` with the errno, and `hauler daemon status` names it. Only a missing socket or a refused connection falls back to the stopped-daemon ledger read.
- 0691e77: `hauler result`, `hauler await`, `hauler kill`, and `hauler request` against a live daemon whose socket cannot be opened now name the errno and point at `hauler daemon status`. They no longer say the daemon starts on demand or to run `hauler daemon start`.

## 0.9.6

### Patch Changes

- 84d9487: `hauler daemon stop` and `hauler daemon restart` no longer hang on a cargo or test binary that ignores SIGTERM. Shutdown now escalates to SIGKILL after `CARGO_HAULER_KILL_GRACE_MS`, as an explicit `hauler kill` already did, and the `cargo metadata` lookup behind dependency-aware estimates follows the same rule.
- 31d165f: `hauler kill` on a ticket waiting in its lane's batch window now settles it killed right away. The kill used to wait out the whole window (`CARGO_HAULER_BATCH_WINDOW_MS`) before the ticket settled.
- 7a29425: `hauler kill` now says what happened to the ticket. A queued ticket reads `killed before it started; no cargo process ran`, a rider reads `killed; detached from cc-N`, and only a running leader still reads `the daemon stops its cargo process and frees the lane`.
- 192a27d: `hauler request` now attributes its ticket to the calling agent. It reads `CARGO_HAULER_HOST` and `CARGO_HAULER_SESSION` like `hauler exec`, then the session id the agent host exports to its shell (`CLAUDE_CODE_SESSION_ID`, `CODEX_THREAD_ID`, `CURSOR_CONVERSATION_ID`). Background requests and PATH-shim runs from an agent shell no longer land in the ledger with a null session.
- 1fe16a4: `cargo-hauler-install install <host>` now rewrites a PATH `cargo` shim that runs another cargo-hauler version, or a `node` or `hauler.js` that is gone, and keeps its Cargo path. A Node or cargo-hauler upgrade no longer leaves scripted cargo on an old client that falls back to passthrough. `cargo-hauler-install doctor` adds that finding to its report as an error, in text and `--json`, and exits `1`. A newly written shim also runs the real Cargo when its embedded `node` is gone, instead of failing every `cargo` with exit 127.
- 7adf6e3: `hauler result` and `hauler await` answer from the ledger when the daemon is stopped instead of exiting with `render-failed`. A ticket the stopped daemon left in flight reads as `orphaned` with the reason `stranded by a stopped daemon`, the same projection `hauler status` and `hauler last` show. A ticket missing from the ledger says so and names the stopped daemon. Both results now carry `daemon` (`running` or `stopped`), and an orphaned ticket gets next-step guidance on every detail surface.

## 0.9.5

### Patch Changes

- c561965: Different agents running the same cargo command in the same workspace share one run again. Request identity no longer hashes the per-conversation ids that Cursor, Claude Code, and Codex export, such as `CURSOR_CONVERSATION_ID`, `CLAUDE_CODE_SESSION_ID`, and `CODEX_THREAD_ID`, and cargo still receives them.

## 0.9.4

### Patch Changes

- 1986024: Keep GitHub cargo-hauler artifacts current by rejecting stale source inputs and executable modes before `pnpm check` rebuilds, and generate next-version artifacts through `release:version` without main self-pushes. (#261)
- ecc05aa: Install cargo-hauler directly from GitHub with framework-generated repository marketplaces and committed plugin artifacts, and preserve Codex settings during refresh. (#259)
- bf3f257: Identical cargo requests share one run again when only shell bookkeeping differs. Request identity no longer hashes `PWD`, `OLDPWD`, `SHLVL`, `_`, or mise's `__MISE_*` session state, which change on every tool call. Other forwarded variables, such as an `OUT` path, still keep requests apart.

## 0.9.3

### Patch Changes

- 093368c: Resolve a symlinked default cache directory before daemon startup while retaining the strict no-symlink policy for explicit `CARGO_HAULER_STATE_DIR` paths.

## 0.9.2

### Patch Changes

- c7eb5c3: Preserve nested Codex MCP overrides when the generated installer replaces an enabled plugin. Refuse replacement when plugin-level enablement is disabled or unknown.

## 0.9.1

### Patch Changes

- cc5540b: Preserve complete inventories when generated plugin installers inspect existing copies.

## 0.9.0

### Minor Changes

- 29a84e7: Require explicit wire-protocol identity, with a one-time daemon stop or restart after upgrading from 0.7.1–0.7.3; remove the `hauler daemon stop --force` alias and stop probing pre-hardening daemon socket paths (#243).

## 0.8.7

### Patch Changes

- 7136e0b: Report ledger rows as orphaned when no daemon answers for them.
- 242cc42: Settle killed queued tickets immediately and remove them from the lane without waiting for earlier work.

## 0.8.6

### Patch Changes

- a827798: Update dev dependencies: react, react-dom, @types/react, @types/react-dom 19.2 -> 19.3, scheduler 0.27 -> 0.28, @effect/tsgo 0.38.0 -> 0.45.0.
- 8fbb2a9: Let `hauler status`, `hauler log`, `hauler daemon status`, `hauler_status`, `hauler_dashboard`, `hauler_last`, `hauler_log`, `hauler_result`, and `hauler_await` read a protocol-compatible newer daemon without `DaemonNewerError`, while `hauler_request`, `hauler_kill`, and Cargo admission keep rejecting it; default omitted `hauler_log` arguments (#234).

## 0.8.5

### Patch Changes

- 99bdc91: Track Agent Bundle `62ffe403c5`, whose runtime declares `zod` as a peer, so the plugin's own `zod@4.6.5` is the only copy the framework types against. `hauler last` is flagless again instead of advertising an unusable `--input <json>` option, and the `kill --ticket`, `request --after`, and `result --full` fields carry their schema descriptions in host forms once more; `hauler_dashboard` gains the `limit` form field it always accepted; and a unit test now re-derives every tool's and CLI route's input metadata from its zod schema so the two cannot drift.

## 0.8.4

### Patch Changes

- f07880f: Adopt Agent Bundle manifest v6 authoring for event handlers, lazy providers, and routed CLI input metadata. (#226)

## 0.8.3

### Patch Changes

- 711f6ea: Identity, coverage, and batch/fold now refuse to share when the forwarded caller environment differs, so two `cargo test`/`check` runs that differ only in `OUT` (or any other variable cargo will see) no longer coalesce onto one result (#222). Duration estimates still key on the compile surface — including when the variable arrives via `env OUT=…` — so one-off output paths do not cold-start EWMA. The PATH shim stays in the foreground for non-TTY callers instead of returning exit 75 and running later against torn-down state; `CARGO_HAULER_SHIM_BACKGROUND=1` restores auto-background for scripts that consume tickets (#223).

## 0.8.2

### Patch Changes

- 7490839: Internal: the implementation behind the Agent Bundle entrypoints now lives
  under `src/internal/<owner>/` (contracts, cargo, daemon/{runtime,broker,
  scheduling,reporting}, storage, client, operations, host-hooks,
  integrations/kache, platform, shim, ui/{documents,dashboard,shared}, util)
  instead of `daemon/`, `lib/`, `hooks/`, and friends; `tests/` is grouped by
  what a test executes (unit, integration, packaging, acceptance). A move-only
  change: no route, executable, protocol, or scheduling behaviour changed.
  `docs/architecture.md` is the ownership map and walkthrough.

## 0.8.1

### Patch Changes

- ef7d00e: Dashboard: the Contention panel's CPU stat is labelled `1-min load average`
  instead of `loadavg (1m)`, which read as a stuck "loading" timer. Same value:
  the machine's Unix 1-minute load average over its cores.

## 0.8.0

### Minor Changes

- 970dbde: New `hauler_dashboard` MCP tool carries the dashboard App
  (`ui://cargo-hauler/dashboard.html`): hosts that render MCP Apps open the
  dashboard beside its result, populated from the same status payload the App
  then polls through `hauler_status`. Its text result is one summary line plus
  where the App and the text form are, so opening the dashboard never pastes the
  status document into the model's context. `hauler_status` no longer advertises
  the App and returns the queue, lanes, and tickets as text for the model, as
  before. `hauler web` opens the App through `hauler_dashboard`. The
  `hauler-dashboard` skill and the status document's dashboard line name the new
  tool.

## 0.7.4

### Patch Changes

- 4267d0c: Keep read-only commands and MCP tools on a protocol-compatible running daemon, and defer automatic replacement for submissions until the older daemon is idle (#209).
  
  Initialize the dashboard on every MCP Apps host — Codex, Claude, ChatGPT, VS Code/Cursor, and Goose — without host-specific origin exceptions (#212).

## 0.7.3

### Patch Changes

- 5d14c4f: Initialize the dashboard in Codex Desktop when MCP Apps use a
  `codex-sandbox://` origin (#206).
- 4ac68a9: Make `hauler daemon stop` and `cargo-hauler daemon stop` preserve typed shutdown outcomes and fail when the daemon refuses, times out, returns a protocol error, disconnects before acknowledgement, or remains alive (#200).
- bc2ed53: Make daemon state and relocated control sockets owner-private on Linux and
  macOS. The state directory, `tickets/`, and the socket's runtime directory
  are created `0700` and sensitive files — complete ticket output, the ledger
  and its WAL sidecars, the daemon log, the passthrough spool, `hook-state.json`,
  `hook-events.jsonl`, the pid lock, and the jobserver FIFO — `0600`,
  independent of the invoking shell's umask, instead of inheriting `0755`/`0644`.
  An existing state directory the running user owns is tightened in place; a
  parent named by `CARGO_HAULER_STATE_DIR` is never chmod'ed, and a state path
  that is a symlink, the wrong kind of entry, or owned by another user is
  refused by name rather than followed, chmod'ed, or deleted. A state directory
  too deep for `sun_path` now puts its socket in a `cargo-hauler-<uid>`
  directory under `XDG_RUNTIME_DIR`/`TMPDIR`/the system temporary directory
  instead of directly in a possibly shared temporary root, and the socket
  digest no longer lowercases Unix paths, so case-distinct state directories no
  longer share one control endpoint. A daemon left listening at the previous
  relocated path is retired by the next client under the existing one-version
  rule and stopped by `hauler daemon stop`, so the moved endpoint needs no
  manual cleanup on upgrade. Windows keeps its named pipe and gains no POSIX
  modes or uids. (#203)

## 0.7.2

### Patch Changes

- 0ca43dd: Compare prerelease versions by SemVer identifiers when deciding whether a client may replace a daemon. Numeric counters such as `rc.10` now sort after `rc.9`, preventing an older prerelease client from being mistaken for a newer install.
- bb0901a: Install PATH shims without following existing symlinks or overwriting other hard links to Cargo executables. Refuse dangling destination links unless `--force` is supplied, publish complete executable files atomically, and prevent an existing destination symlink from being embedded as the shim's own real Cargo path.

## 0.7.1

### Patch Changes

- 9a50169: Fail closed with exit 69 when a reattached `hauler exec` cannot restore the complete Cargo output stream, and serialize overlapping reattach claims so a stale connection cannot replace the latest ticket owner (#187).

## 0.7.0

### Minor Changes

- 80939ab: A `hauler exec` whose daemon connection drops after the ticket was accepted now
  reattaches instead of giving up. The client prints `connection to daemon lost;
  reattaching to ticket cc-N…`, reconnects (starting the daemon again if it is
  gone, a few attempts one second apart), and sends the new `reattach` message;
  the daemon rebinds the ticket, replays the output the client had not yet
  received, and streams the rest, so the caller's exit code is cargo's as if the
  connection had held. Output the replay buffer no longer holds is announced as
  `N bytes of output missed while reconnecting; full log: <path>`, never
  invented. A ticket that finished in the meantime yields its exit code and log
  path.
  
  The daemon no longer kills a queued ticket the moment its submitter
  disconnects: it keeps its queue position (and may start) for
  `CARGO_HAULER_REATTACH_GRACE_MS` (default 30000; `0` restores the immediate
  kill) and is killed as `killed while queued: submitter disconnected and did
  not reattach within 30s` only if nobody reattaches. A running ticket continues
  as before, marked orphaned; a reattach clears the flag.
  
  When the ticket cannot be reattached — it never ran cargo and was killed at
  the daemon's shutdown or `orphaned by daemon restart`, the daemon does not
  know it, the daemon predates this release and answers the message with
  `bad-message`, or no daemon answered within the budget — the client fails
  closed with `brokered run aborted: daemon connection lost; ticket cc-N
  <reason>` and the new exit code `69` (`EX_UNAVAILABLE`), distinct from cargo's
  `1` and from `2`/`75`/`130`/`143`. It no longer prints `ticket cc-N continues
  — hauler result cc-N` and exits `1` while the daemon records `killed while
  queued`. `--bg` and auto-backgrounded tickets are detached, not owned, and
  are unchanged. (#187)
- 3758d82: Refuse a target directory shared across workspace roots (#185). When a request's target dir lies outside its own workspace and another lane with a different workspace root already uses that dir, the daemon refuses it as a bad intent (client exit 2) with a message naming both roots, the dir, and the mechanism: cargo's `-C metadata` hash is relative to the workspace root, so same-layout worktrees write identical artifact filenames into a shared target dir and whichever compiled last runs as "fresh" in the others — a stale-binary collision, not a kache miss. `hauler exec --allow-shared-target` or `CARGO_HAULER_ALLOW_SHARED_TARGET=1` (per request, or in the daemon's environment for all requests) admits the request with one warning line on stderr that cargo's `--quiet` cannot hide. `hauler status` and the dashboard flag every lane on a shared dir with `sharedTargetWith` (optional; older daemons omit it). Refused intents — this one and the existing bad-intent rejections — are now ledgered as `denied` rows instead of `failed` runs under lane `invalid`, so `hauler log` no longer shows a command that never ran as a failed run.

### Patch Changes

- f84e901: `hauler exec --allow-shared-target` (and `CARGO_HAULER_ALLOW_SHARED_TARGET=1`
  in the caller's environment) now reaches the daemon: the server dropped the
  request field, so only the daemon-side setting could admit a shared target dir.
  The shared-target warning has one wording everywhere — the daemon's refusal
  and ack line, the lane board, and the dashboard's lane cell (other roots, full
  text on hover) — and `hauler request` shows the daemon's warning in its summary
  when the daemon admitted a shared target. The `hauler status` summary string
  no longer carries extra warning lines the document never rendered; the lane
  board is where the warning shows.
  Detection no longer re-resolves already-canonical lane paths on every submit
  and status poll.

## 0.6.18

### Patch Changes

- d344246: The `cargo-hauler` skill no longer tells agents how to set `CARGO_TARGET_DIR`. The one bullet that did ("do not hand-roll `CARGO_TARGET_DIR` isolation … the daemon already serializes per (workspace, target dir)") read as if sharing one target dir across checkouts were safe because runs are serialized; it is not — cargo's `-C metadata` hash is relative to the workspace root, so worktrees of one repo with the same layout collide on artifact names and stale binaries run silently (#185). Target-dir policy belongs to the operator and, where hauler can detect the footgun, to the daemon, not to the prompt. (#186)

## 0.6.17

### Patch Changes

- 9da56e4: Preserve shell-hook denials and governed rewrites when telemetry fails, times out,
  or is cancelled. Bound recording waits and emit command-free diagnostics. Keep
  probe errors explicitly host-decided rather than reporting confirmed daemon absence.
- fcedc8c: Bind shell rewrites to the semantic request's observed plugin code root. Delegate
  standalone root validation to the public framework resolver with explicit fallback
  policies, and remove the product's native host environment precedence chain. (#181)
- 6595017: Report prerequisite-blocked tickets as never run in result/await headlines and
  guidance. Explain how to rerun the prerequisite and resubmit instead of claiming
  there are diagnostics to fix or that a retry must attach to the blocked ticket. (#177)
- 217c4c4: Report all observed test-binary summaries from shared test logs in result,
  await, and last, with explicit composite-output and widened-filter labels
  instead of another package's final output tail. (#180)

## 0.6.16

### Patch Changes

- dded7d3: Adopt the Agent Bundle opaque-origin App client so Codex can connect to the cargo-hauler dashboard. (#164)
- 5765610: Keep dashboard payload handling synchronized with generated Agent Bundle route result types. (#165)
- cb5b4dd: Delegate `cargo-hauler-install` to `agent-bundle/install`, publish the plugin root at `dist/`, and replace `install --plan` with `doctor --host` or `uninstall --plan`. (#160)

## 0.6.15

### Patch Changes

- 5f95b51: Stop changing source package modes before Agent Bundle validates install manifests. (#154)

## 0.6.14

### Patch Changes

- 1805d07: Select semantic event routes with Agent Bundle capability requirements instead of host names (#149).

## 0.6.13

### Patch Changes

- 16bbf46: Use Agent Bundle route references and workspace context for hauler requests, and stop probing daemon health from the global layout (#147).

## 0.6.12

### Patch Changes

- cc1ebc7: Install from the published `artifact/` root and restore manifest file modes so `cargo-hauler-install install <host>` works after npm rewrites executable bits. (#145)

## 0.6.11

### Patch Changes

- 0d87d1d: Point the published `bin` map at the files the tarball ships (`cargo-hauler` → `dist/bin/cargo-hauler.mjs`) and stage `cargo-hauler-install` so `cargo-hauler-install install <host>` works after `npm i -g`. `cargo-hauler-install install <host> --plan` prints the artifact identity without changing a host (#143).

## 0.6.10

### Patch Changes

- 58f0da4: Hand the `tool/after` preflight's session-completed answer to the route as `preflight` data so the after-shell hook does not query the daemon a second time, and pin `agent-bundle` and `@agent-bundle/runtime` to the preview of main `9197015b9` (agent-bundle #661/#664).
- 40c5dc4: Make the kache snapshot-reader event-loop test count observed loop turns during a large refresh instead of bounding a 1 ms `setInterval` gap, so the check stays deterministic on shared runners including macOS.

## 0.6.9

### Patch Changes

- e160307: Print usage and exit 2 when `hauler daemon` is missing or given an unknown subcommand, and accept `hauler daemon stop --force` as a no-op alias for stop (#138).
- 8dfe1c3: Flag cargo that ran outside the broker. An agent that wraps cargo in a script calling the toolchain binary by absolute path (`~/.rustup/toolchains/*/bin/cargo`) slips past both the `tool/before` rewrite (the command never says `cargo`) and the PATH shim (never on PATH), so its builds get no lane, no attach, and no ledger row while the machine saturates. The `tool/after` preflight and route now recognise cargo's status lines (`   Compiling …`, `    Finished … profile`, `     Running …`) in the output of a command that named neither cargo nor hauler, record the call in the hook log with reason `cargo ran outside cargo-hauler`, and tell the agent to name `cargo` in the command or run `hauler exec -- cargo …`. A file reader showing a saved log (`tail build.log`) is not treated as a run. The skill now says not to wrap cargo that way — besides leaving the broker, a toolchain binary called directly sets no `RUSTUP_TOOLCHAIN`, so registry crates compile under the default toolchain and fail with `E0514` — and how to drop a rustc wrapper or pin a toolchain through the broker instead.

## 0.6.8

### Patch Changes

- bf9d3a4: Move to agent-bundle's composite plugin root and framework surfaces: `artifact/` is the one root every host installs from (`cargo-hauler-install install <host>`, `node artifact/install.mjs`, the host plugin commands) in place of `artifact/<host>`; `hauler dashboard` is replaced by the installed plugin's own `bin/cargo-hauler.mjs web`; the dashboard App talks to its host through `createAppClient`; the `tool/before` and `tool/after` shell hooks are event routes with preflight gates instead of config-declared handlers; and `hauler status`, `log`, `last`, `await`, `result`, `request`, and `kill` are the MCP tools' own CLI projections, with unchanged flags and output (#107)

## 0.6.7

### Patch Changes

- 592863f: Make daemon replacement directional. Upgrading with long-lived sessions still on a previous plugin produced a war: the old clients shut down the new daemon on every hook call (and failed to start their own), the new clients started it again, and no daemon lived longer than two seconds. Now only a newer install replaces a daemon. The `shutdown` request carries the client's version; the daemon refuses a shutdown from a client older than itself or from one that sends no version (`shutdown-refused`), and a client that finds a newer daemon fails as `DaemonNewer` without touching it: `hauler exec` runs cargo directly with that reason, hooks continue, and `hauler status` / `hauler daemon status` name the newer daemon. `hauler daemon stop` from an older install is refused the same way.

## 0.6.6

### Patch Changes

- 60fd579: Spawn the daemon from an absolute entry path. The daemon runs with the state dir as its cwd, so a relative entry (a host running the plugin script from its own directory, or a relative plugin root) resolved to `<state dir>/scripts/hauler.mjs` and every start died with `MODULE_NOT_FOUND`; with version gating that repeated on each hook call after an upgrade. The entry is now resolved against the client's cwd and checked for existence, falling back to the package entry beside the bundled module and only then to the running script.

## 0.6.5

### Patch Changes

- 4686a3b: Lane scheduling prefers the compile surface the lane just built. A queued request whose profile, features, target, toolchain, or compile-relevant environment differ from the last leader's is scored as 1.5× its estimate, so consecutive requests that share a surface run back to back instead of alternating `cargo test` (test profile) with `cargo check`/`build` (dev profile) or flipping a feature set on every ticket. The ledger showed 47% of consecutive same-lane runs switching surface and those runs taking 1.6–2.5× as long as same-surface runs. Shortest-job-first, fan-out, edit fail-fast, and age escape still apply: a switch that is clearly cheaper still runs first, and a switching request cannot be starved.

## 0.6.4

### Patch Changes

- 1baf004: Model `env NAME=value … cargo …` and `bash -c '… cargo …'` requests as the cargo they run (#126). A leading `env` (assignments, `-u NAME`) is folded into the request environment and intent key, so such requests get the right subcommand, packages, estimate, identity/coverage attachment, batching, demux, and execution-phase hand-back instead of being recorded as subcommand `env` with a default estimate. A `bash -c` / `sh -c` / `zsh -c` script with exactly one cargo statement is scheduled, estimated, and phase-tracked as that cargo tail, but never attaches, leads, or folds (the script may do more than its cargo), and is never demuxed. `env -i` stays opaque; other `env` options are refused as non-cargo programs.
- 1baf004: Serialize `hook-state.json` read-modify-write cycles across concurrent sessions' hook processes with a lock beside the file (#110), so one session's completion cursor or stop-denial counter is no longer lost when another session's hook saves at the same moment. Waiting is bounded (2 s) and a lock that cannot be taken degrades to the previous unlocked update rather than failing the host's tool call; a lock older than 5 s is treated as abandoned.
- cc3e6bc: State precisely when `hauler install-shim` embeds the running npm entry instead of the PATH `hauler` (only when no `hauler` on PATH resolves to a `.js` script; version-manager shims do not count), in the README and in the command's own message, which no longer calls a self-embedded checkout entry "global" (#124)
- 1baf004: `hauler status`, `hauler daemon status`, `hauler log`, `hauler_status`, and the dashboard no longer die with a `ZodError` when the daemon that answers is from a previous install (#123). The read-only surfaces check the daemon's reported version before parsing its rows; a mismatch reads the ledger, marks the daemon `unresponsive`, and says which version is running and that the next cargo command or `hauler daemon restart` replaces it.
- e0ef261: Replace a daemon from a previous install before `hauler status`, `hauler daemon status`, ticket reads, MCP tools, or hooks parse its reply; report a clear failure when replacement cannot finish (#127)

## 0.6.3

### Patch Changes

- fa1415d: Ignore the vendored `repos/effect` subtree in Renovate (#108).
- 3bc0131: Serve `hauler dashboard` through `spawnServeApp` from `agent-bundle/serve-app-command` instead of hand-rolled child-process plumbing, and re-pin the `agent-bundle` / `@agent-bundle/runtime` preview to main `d30d9acb6` (agent-bundle #582 `serve-app-command` + `AB4837`, #588 package `dist/` held to `AB6005`). Flags (`--target`, `--port`, `--no-open`), the opening `hauler_status` call, and the checkout-only scope are unchanged; the helper's typed failures (`framework-not-installed`, `artifact-missing`, `spawn-failed`, `exited-before-ready` with the child's exit code, `aborted`, `stop-failed`) become the command's message, and a Ctrl-C whose stop the server refuses now ends the command with that failure instead of hanging. (#122)
- c5694dc: Make `hauler install-shim` embed the global PATH entry and make plugin-local `scripts/hauler.mjs` refuse direct CLI use (#121)
- 476cd52: Make `hauler status`, `hauler_status`, and `hauler log` rows bounded summaries that never carry an output tail; read a ticket's whole tail with `hauler result` / `hauler_result`, `hauler await`, or `hauler last`. (#95)
  
  - The status document used to overlay every running ticket's whole in-memory tail (up to 16 KiB per running row) onto its status row, and the dashboard polls that document every 5 s. A status or log row is now a summary (`StatusRow`, `statusRowSchema`): no `outputTail`, settled or live. A running row carries `outputPreview`, the last 8 lines (at most 512 bytes) of its live output cut at a line boundary; every other row has `outputPreview: null`. The text rendering of `hauler status` never printed tails, so only `--json` / `structuredContent` readers see the difference.
  - The whole tail is the detail contract: `hauler result <ticket>` / `hauler_result` and `hauler await` carry a finished ticket's settled 16 KiB tail or a running ticket's whole live tail, and `hauler result --full` the on-disk log. `hauler last` reads the newest ticket as a detail record — from the daemon while it is running, otherwise from the ledger — so it shows the tail again.
  - The dashboard's in-flight rows show the last preview line under the command; the ticket drawer always fetches `hauler_result` and refreshes the tail every 3 s while the run is in progress, so a drawer opened from a summary row shows the whole output.

## 0.6.2

### Patch Changes

- 10500db: Fold `cargo test` runs that share an identical `--exact` harness flag.
  
  `--exact` joins `--test-threads=N`, `--nocapture`, and `--quiet` as a harness flag a `cargo test` composite carries: libtest OR-s its positional filters and applies `--exact` to each, so `cargo test -p a -- x::y --exact` and `cargo test -p b -- z::w --exact` now run as `cargo test -p a -p b --no-fail-fast -- x::y z::w --exact` — the union of packages with the union of filters, the flag once — with the same cross-package name-collision caveat the substring fold already accepts (under `--exact`, only a test of the same full name in another package). `--exact --nocapture`, the single-test shape agents emit, folds the same way. The harness set must still be identical on every participant, so exact and substring matching never mix: a run with `--exact` never joins a composite without it, and vice versa. Everything else holds: the `--test` / `--lib` selection must match, unfiltered runs fold only with unfiltered runs, requests naming the same packages still need the same filters (#53), a participant inherits a composite failure only when it asked for every package and every filter the composite ran and otherwise reruns alone (#87), and `--skip`, `--ignored`, `--include-ignored`, `--list`, `--format`, `--logfile`, or any other harness flag keeps a run out of composites. A composite now splices the followers' filters in behind the leader's own, ahead of its harness flags (`-- f1 f2 --test-threads=4` rather than `-- f1 --test-threads=4 f2`); libtest reads both alike. (#97)

## 0.6.1

### Patch Changes

- 3aded69: Replace the version-skew warnings with a one-version rule, and stop reading the `CARGO_CONDUCTOR_*` names. The CLI, hooks, MCP server, and daemon ship in one npm package, so a daemon from another version is replaced rather than talked to: when `hauler exec`, `hauler request` (and `hauler_request`), a hook rewrite, or `hauler daemon start` reaches a live daemon whose version differs from the running build's, it sends the graceful shutdown request, waits up to 5 s for the old pid to exit, and starts a daemon from this install — the same mechanics as `hauler daemon restart`, which remains the manual, unconditional form. Tickets in flight on the old daemon are not handed over: the new daemon's first ledger pass marks them `killed` with `orphaned by daemon restart`, and callers resubmit. An old daemon that has not exited within the grace is reported, never killed: the call fails with `` cargo-hauler daemon pid N (X.Y.Z) is still running 5s after the shutdown request; not restarted — retry once it has exited, or stop it with `hauler daemon stop` `` and nothing is started; `hauler exec` then runs cargo directly, naming that reason, and `hauler daemon start` exits 1. Read-only surfaces — `hauler status`, `log`, `last`, `result`, `await`, their MCP tools, and the header probe — never replace a daemon; they read the one that is there. Removed with the skew subsystem: the `daemon 0.4.2 ≠ cli 0.4.4 — restart it with …` line in document headers, in the session-start notice, and in the status summary; the `daemonVersion` field of `hauler status --json` and the `hauler_status` result (`pid`, `startedAtMs`, and the rest are unchanged); the `daemon is 0.4.1 (pid N, since 3h ago), this CLI is 0.4.4 — restart …` failure and its "first mismatch" second line; the extra `ping` that asked a daemon for the version its status report did not carry (every report carries `version`); and the lenient client schemas — `outputPath` and `after` are required again, read exactly as the same-version daemon sends them. The dashboard tiles that could read "unavailable from an older daemon" no longer have that state, and the one-time recompute of attachment savings on ledger rows written before 0.5 no longer runs (those rows keep the numbers they have). `CARGO_CONDUCTOR_*` is no longer read; set `CARGO_HAULER_*`: `CARGO_CONDUCTOR_MAX_CONCURRENT`, `CARGO_CONDUCTOR_KACHE_INDEX`, `CARGO_CONDUCTOR_REPLAY_BUFFER_BYTES`, `CARGO_CONDUCTOR_JOBS_GRANT`, `CARGO_CONDUCTOR_BATCH`, `CARGO_CONDUCTOR_BATCH_WINDOW_MS`, `CARGO_CONDUCTOR_LOAD_THRESHOLD`, `CARGO_CONDUCTOR_LOAD_MIN`, `CARGO_CONDUCTOR_CPU_PRESSURE_THRESHOLD`, `CARGO_CONDUCTOR_KILL_GRACE_MS`, `CARGO_CONDUCTOR_LOG_LEVEL`, `CARGO_CONDUCTOR_STOP_WAIT_MS`, `CARGO_CONDUCTOR_CARGO_BIN`, `CARGO_CONDUCTOR_HOST`, and `CARGO_CONDUCTOR_SESSION` are ignored, the generated PATH shim checks only `CARGO_HAULER_INSIDE`, the stderr warning about an exported `CARGO_CONDUCTOR_STATE_DIR` is gone, and `conductor` is no longer a word the shell hooks treat as a cargo mention — only `cargo` and `hauler` are. (#105)

## 0.6.0

### Minor Changes

- cfb8d3c: Make coverage attachment fire in practice, say why it did not, and let a compile-only `cargo test --no-run` ride a running `cargo test`.
  
  - A `test --no-run` / `bench --no-run` submitted while a `test` / `bench` with the same packages, target selection, features, and profile is in flight in the lane attaches to it and is released — `done`, exit 0, with the leader's compile time so far as its saved compute — the moment the leader prints Cargo's `Finished` line, while the leader goes on running its tests. The leader's test filters, `--test-threads`, and `--no-fail-fast` do not disqualify the rider; a `--lib` rider never attaches to a `--test foo` leader, or a named-target rider to a default-target one. A leader whose build fails requeues the rider through the existing failed-stronger path; a leader that never reports its build finished (`--quiet`, `CARGO_HAULER_OVERLAP_EXECUTION=0`) releases it at settlement. A rider that arrives after the leader's build already finished is refused (`leader-build-finished`) and runs its own, mostly fresh, cargo at once. Both rows carry `buildFinishedAtMs`. (#88)
  - Coverage gates widened where safe: flags the daemon does not model (`--locked`, `--offline`, `--message-format`, …) and arguments after `--` disqualify a `check` riding a `build` / `check` only when they differ between the two requests; `check --tests` rides `build --tests` / `build --all-targets`; a `--bin X` / `--example X` rider rides a `--bins` / `--examples` leader. `check` still never rides `clippy` (a lint failure would misreport the check), a `-p` rider is still not proven by a leader compiling its unnamed default package set, and `--tests` does not stand in for a named `--test X`. (#89)
  - Every refused attachment is logged at debug level (`attach rejected`) with the gate — `subcommand`, `opaque-arguments`, `passthrough`, `compile-surface`, `packages`, `targets`, `channels`, `leader-build-finished` — a one-line detail, and both tickets; the status report counts one refusal per request under `metrics.attach_rejections`, keyed by the nearest gate any leader reached (`hauler status --json`, `hauler_status`). (#89)
- d5ae3e9: Show what the hauler costs, not only what it saves: queue wait vs run time per metrics window, the compile/execute split per command, and kache store pressure. (#92)
  
  - Each metrics window (`1h`, `24h`, `all`) now reports total queue wait against total run time for leaders, and splits the wait by cause: *lane-bound* (a same-lane leader was still compiling — before its `Finished` line or exit), *permit-bound* (every admission permit was held and no same-lane compile was to blame), and *other* (admission holds, `--after` prerequisites, scheduling latency). The classification is a pure sweep over ledger rows, run against the daemon's current permit count; the tile states that assumption, since runs admitted under an earlier cap are classified against today's.
  - With `buildFinishedAtMs` on the row, the by-command split adds compile vs execution p50s for `test`/`run`/`bench` leaders, and the window reports the lane time the execution-phase hand-back released. Pure compiles have no split and say nothing.
  - The kache panel (`hauler status`, `hauler_status`, and the dashboard) surfaces store pressure: blob bytes recorded in the index against `local_max_size` (from `KACHE_MAX_SIZE` or kache's `config.toml`; "limit unknown" names why when it cannot be read), the last GC from `gc_stats.json` — when, how long, what it evicted — with any `gc: skipping eviction` warnings matched from kache's `auto-gc.log`/`daemon.log` during that run, and `key_ms` mean/p95 over the events tail. Warnings appear when the store is over its limit or the last GC declined or skipped evictions. Missing or unparsable files render as unavailable with their reason, never as an empty store.
  - Protocol additions are optional fields (`metrics.windows[].waitSplit`, `handBack`, `runTotalMs`, `waitTotalMs`, `bySubcommand[].phases`, `kache.pressure`); clients reading an older daemon see the tiles as unavailable rather than as zeros.
- ec9bf65: Fold more queued work into one Cargo run: compile batches with a `--` trailer, and test runs whose filters differ.
  
  - `cargo build` / `check` / `clippy` requests with the same arguments after `--` now batch: `cargo clippy -p a -- -D warnings` and `cargo clippy -p b -- -D warnings` run as `cargo clippy -p a -p b -- -D warnings`, the trailer once. Such runs are demultiplexed like any other compile (the `--message-format` rewrite goes before the `--`), so under `-D warnings` a participant whose own units compiled cleanly is released as done when another participant's warnings fail the composite; the rest rerun alone. Trailers that differ, or a trailer on one side only, still keep the runs apart, as do differing `--all-targets` / `--tests` / `--features`. (#86)
  - `cargo test` requests naming different packages fold even when their bare name filters differ: `cargo test -p a -- f1` and `cargo test -p b -- f2` run as `cargo test -p a -p b --no-fail-fast -- f1 f2`, the union of packages with the union of filters. An identical set of `--test-threads=N`, `--nocapture`, `--quiet` (or `-q`) after `--` folds too, carried once from the leader; a mismatched set does not, and `--exact`, `--skip`, `--ignored`, `--include-ignored`, `--list`, `--format`, `--logfile`, or any other harness flag keeps a run out of composites. `--lib` runs fold like `--test NAME` runs; the target selection still has to match. Requests naming the same packages still need the same filters, and unfiltered runs fold only with unfiltered runs. A folded participant inherits a composite failure only when it asked for every package and every filter the composite ran; otherwise it reruns alone. (#87)
- c27662d: Estimate compile and execute separately, and keep a realistic queue ETA when a live head overruns.
  
  `hauler status` reports `compileEstimateMs`, `executeEstimateMs`, and the current `phase` on a ticket. Followers waiting on a `test`/`nextest`/`bench`/`run` head see only the remaining execution time in `queue.waitEtaMs` (zero once overlap has handed the lane back), and queued tickets ahead of them count only their compile when overlap will hand the lane back. A head that has passed `CARGO_HAULER_STALL_ESTIMATE_FACTOR` times its estimate but is still burning CPU or printing is flagged `estimateState: overrun` with the intent's p90 (`p90Ms`), and its followers see `queue.headEstimateState: overrun`; its remaining time is re-estimated from that p90 — never less than one more estimate's worth — instead of contributing zero to the queue. A silent no-CPU head stays `stalled`. Filtered `--test <name>` runs with no history of their own borrow a same-package neighbor's timing before falling back to the crate-wide prior.

### Patch Changes

- f01833d: Cut what the hooks cost every shell tool call. The `tool/before` and `tool/after` hooks are now declared handlers compiled into standalone entries (`hooks/before-tool-shell-before-*.mjs`, `hooks/after-tool-shell-after-*.mjs`) instead of event routes that loaded the whole rendering runtime before looking at the command. `tool/before` reads `tool_input.command` and answers `continue` at once when it has no `cargo`, `hauler`, or `conductor` word (word-boundary aware; `mycargo` and `CARGO_HOME=… ls` do not match, `~/.cargo/bin/cargo`, `cargo-hauler`, and `echo cargo` do — a doubtful command takes the full path). `tool/after` runs that test plus one bounded 500 ms socket ping (`session-completed` with the session's hook-state cursor, no Effect runtime), and loads the telemetry and notification code only when the command was cargo-related or the daemon reported finished tickets; with no daemon it exits quietly with no output. Rewrite, `cargo clean` denial, `hauler exec` pass-through, and completion notices are unchanged. Measured with `/usr/bin/time -v`, median of 5, Claude `ls -la` envelope: `tool/before` 0.05 s / 48 MB and `tool/after` 0.05 s / 49 MB, against 0.54 s / 143 MB and 0.56 s / 144 MB for the 0.5.0 wrappers without a shared runtime (0.10 s / 64 MB with one, per #90); a `cargo test -p foo` envelope is 0.05 s / 49 MB either way. (#90)

## 0.5.0

### Minor Changes

- b80d9db: Cut the latency the hauler adds on a shared target directory, and stop charging lane wait to attaching.
  
  - A lane now hands its slot to the next request as soon as a `test`, `nextest`, `bench`, or `run` leader prints Cargo's `Finished … target(s)` line: Cargo drops the build-directory lock there, so the next compile overlaps the leader's test run instead of waiting for it. The leader keeps its admission permit until it settles. `hauler status` and the dashboard list such leaders under the lane's `executing` tickets, and the ledger stamps `buildFinishedAtMs` on the leader and its riders. `CARGO_HAULER_OVERLAP_EXECUTION=0` restores strict one-process-per-lane.
  - Default admission permits scale with the machine: one per eight cores, clamped between the previous five and sixteen. `CARGO_HAULER_MAX_CONCURRENT` still overrides.
  - The cost model times edited and cached runs of an intent separately, and floors an edited intent that has only ever been timed cached at the crate compile priors, so a recompiling test run no longer reaches the head of the lane on a seconds-long estimate; unedited runs no longer teach the per-crate compile priors.
  - Saved latency for riders is measured from the later of the rider's creation and the leader's start: time queued behind a leader that had not started is lane wait the rider would have paid alone, not a cost of attaching. Existing ledgers recompute the column once on open (`user_version` 2); the dashboard's all-time latency tile changes accordingly.

## 0.4.9

### Patch Changes

- 395cee0: Re-pin `agent-bundle` and `@agent-bundle/runtime` to the `c2ffe5ec5` preview and drop the workarounds it retires. `hauler await` and `hauler_await` wait up to the daemon's 2 h ceiling in one call (`maxWaitMs` ≤ 7200000): the routes declare `config.render.maxElapsedMs` instead of clamping every wait to 55 s, and the await guidance names that ceiling; a host with its own per-call deadline (Codex `tool_timeout_sec`) still bounds one call. The `tool/before`, `tool/after`, and `stop` hook routes read the framework's canonical event payload (`sessionId`, `toolInput`, `toolResponse`, `reentry`) instead of parsing Claude, Codex, and Cursor keys themselves; the never-prompt semantics are unchanged — `allow` only for a fully hauler-governed rewrite, `continue` otherwise, never `ask`. `hauler exec` shapes color, `2>&1` merging, and the redirected-stdout notice from the terminal the executable envelope hands `main`, no longer probing `process.stdout` itself. Ticket lineage accepts the runtime's `transcript` resolution. The rendered `hauler-dashboard` Skill names the release version again. New: `hauler dashboard [--target HOST] [--port N] [--no-open]` serves the dashboard App in a plain browser tab against the running daemon (through `agent-bundle serve-app`, from the checkout or the npm package). (#83)
- 23b3bb8: MCP server stands by instead of exiting when another session owns the event-runtime socket; takes over when it exits (agent-bundle #559/#561). Every host session launches its own `hauler` MCP server from the same installed pack, and only one of them can own the pack's event-runtime Unix socket. Until now the second server exited before answering `initialize` (`Event runtime endpoint already has a live server.`), so a second concurrent Claude Code, Codex, or Cursor session saw no `hauler_*` tools at all. Rebuilt against the `agent-bundle` / `@agent-bundle/runtime` preview of main commit `cd0b4a62c`: a server that finds the socket owned now starts in the standby role, serves its own MCP session, probes the socket about once a second, and takes it over when the owner exits — both transitions are announced on stderr only (`… is owned by another process; standing by`, `… was released by its owner; took it over`), and a takeover failure other than "still owned" is reported there too. Development only: the `@agent-bundle/runtime` preview now depends on the same-commit preview of `rsc-markdown-stream`, so `pnpm-workspace.yaml` sets `blockExoticSubdeps: false` for the build; the published package still declares no runtime dependencies. (#85)

## 0.4.8

### Patch Changes

- 3ad6b7e: Declare no runtime `dependencies`: `npm install -g cargo-hauler` fetches one tarball and nothing else. 0.4.7 listed the build-time stack (`@agent-bundle/runtime` as a pkg.pr.new tarball, `bashjsast` as a `github:` ref, `effect`, `react`, `zod`, …) under `dependencies` even though every shipped file bundles what it uses, so npm 12's default `allow-remote=none` / `allow-git=none` refused the install (`EALLOWREMOTE` for the tarball, `EALLOWGIT` for the git ref). The whole stack now lives under `devDependencies`; the emitted packs and `dist/bin` executables are unchanged. (#82)

## 0.4.7

### Patch Changes

- f13360f: Internal simplification sweep: one socket-errno walker (`src/lib/socket-errors.ts`) serves the client, the health probe, and the hooks; `defaultCargoProfile`, the ms clamps, and `diagnosticCounts` are shared instead of copied; request statuses and attach modes are one `as const` list that feeds the types, the zod schemas, and the ledger's SQL filters; the ack is the broker's `SubmitResult` spread once; the before-shell hook skips the bash parse for commands that cannot name cargo, and the after-shell hook no longer rewrites its state file when no ticket finished. The `hauler await` heartbeat spells whole minutes as `2m` rather than `2m0s`.

## 0.4.6

### Patch Changes

- 4257982: Survive a CLI or plugin upgrade under a daemon left running from the older install, and add `hauler daemon restart`. The client schemas now default the fields older daemons never send (`outputPath`, `after`), so `hauler status`, `hauler result`, `hauler await`, and the `hauler_*` tools read a 0.4.1 daemon's replies instead of printing a Zod issue array; a reply that still cannot be read fails as `daemon is 0.4.1 (pid N, since 3h ago), this CLI is 0.4.4 — restart it with \`hauler daemon restart\`` with the first mismatch (`active[0].outputPath expected string, received undefined`) on a second line, and `hauler daemon status` says the same. The status report carries the daemon `version` (older daemons are asked with one `ping`), `hauler status --json` exposes it as `daemonVersion`, and every document header — plus the status summary — warns `daemon 0.4.2 ≠ cli 0.4.4 — restart it with \`hauler daemon restart\`` while the versions differ. `hauler daemon restart` sends the graceful stop, waits up to 5 s for the old pid to exit, starts a daemon from this install, and prints both pids and versions (`previousPid` in the JSON result; exit `1` when the old daemon did not exit, in which case nothing is started). Tickets in flight at a restart are not handed over: the new daemon marks them `killed` with `orphaned by daemon restart`, and `hauler result` / `hauler_result` explain that instead of showing a plain kill. The shell header ends with `state dir <path>` so a moved `CARGO_HAULER_STATE_DIR` is visible on every command. (#75)

## 0.4.5

### Patch Changes

- 62d4e16: Add `--after <ticket>[,<ticket>…]` to `hauler exec` and `hauler request` (and `after: string[]` on `hauler_request`) to declare explicit ticket dependencies: the request stays `queued` — skipped by lane admission and batch folding — until every named ticket has settled, fails with `prerequisite cc-N failed`/`killed` (exit code `null`) if one of them does, resolves at once when they already finished, and is rejected as a `bad-intent` error naming an unknown ticket. Prerequisites may live in another lane; the ledger stores them (`after` on every request record) and `hauler status`/`hauler result`/await heartbeats show `waits for cc-N (running 2m/~5m)` while a dependent is held. The `exec --bg` and `request` acknowledgements now name the tickets ahead in the lane (`queued behind cc-3281 (~13m)`, `ahead` on the ack and `queue` on the request result) so a cost-ordered reorder is visible, and `hauler request` reports the daemon's rejection reason instead of a generic submit failure. (#45)
- 1135585: The daemon rejects a path-shaped program that is not cargo (`bad-intent`) instead of running it and recording the path as the subcommand; the dashboard's by-command table shows legacy rows by basename, and the all-time latency tile reads "latency added by attaching" when riders waited longer than their solo estimate instead of showing a negative "saved" value. Dashboard screenshots refreshed.
- d86d8b6: The shared fifo jobserver is armed only when the host `make` can speak it (GNU make 4.4+, or no make at all); on older makes the daemon falls back to per-run `CARGO_BUILD_JOBS` grants so `-sys` crates whose build scripts run `make` (jemalloc, openssl, …) build again. `CARGO_HAULER_JOBSERVER=fifo|off|auto` overrides the detection (#76).
- b13db35: Detect stalled tickets and end orphaned ones. The daemon samples every running ticket's process-tree CPU time (Linux `/proc`, macOS `ps`) and flags a run `stalled` once it is past `CARGO_HAULER_STALL_ESTIMATE_FACTOR` (3) times its estimate with no CPU and no output for `CARGO_HAULER_STALL_IDLE_MS` (10 min). `hauler status` / `hauler_status`, the dashboard, and `hauler await` heartbeats show `stalled` with the idle duration; `hauler result` / `hauler_result` answer `ticket looks stalled (no CPU for Nm) — hauler kill cc-N`. A stalled ticket whose submitting connection has disconnected is killed automatically through the `hauler kill` path with the error `stalled: no CPU for Nm after owner disconnected; killed automatically`; `CARGO_HAULER_STALL_AUTO_KILL=0` only flags. Status records gain optional `stall` and `orphaned` fields. (#46)
- cfb6967: Keep every ticket's whole output on disk and make it readable after the fact. The daemon writes a leader run's combined stdout+stderr, as emitted (rendered diagnostics for demultiplexed runs, ANSI as captured), to `<state dir>/tickets/<ticket>.log`, bounded by the new `CARGO_HAULER_TICKET_LOG_MAX_BYTES` (default 64 MiB, then one truncation line; `0` disables). The ledger gains `output_path`, exposed as `outputPath` on every request record (attached followers carry their leader's path); the startup retention pass removes the logs of pruned rows and any log without a row. `hauler result <ticket>` now shows `Full output: <path> (size)` and `--json` carries `request.outputPath`; `hauler result <ticket> --full` and `hauler_result { ticket, full: true }` render the whole log as the document body (the last ~768 KiB when it does not fit, with the path for the rest). When a synchronous command is auto-backgrounded and the caller's stdout is not a terminal, the exit-75 notice adds that the redirect receives no output and to read it with `hauler result cc-N --full`. (#68)

## 0.4.4

### Patch Changes

- 92e168c: Bump `agent-bundle` and `@agent-bundle/runtime` to the pkg.pr.new preview at `42539ff5f`. With agent-bundle#461 a pass-through `tool/before` result no longer auto-approves, so the hook now answers `allow` only when every command in the input was rewritten onto (or already runs through) the hauler exec path, `continue` + `updatedInput` when a rewritten cargo command shares the input with something the daemon does not govern (`cd … && cargo build`, `cargo test | tail`), and plain `continue` (no decision) for every other tool call; it never returns `ask`. Accept the `confirmed` lineage resolution (agent-bundle#486) in ticket attribution.

## 0.4.3

### Patch Changes

- a2c39f3: Bump `agent-bundle` and `@agent-bundle/runtime` to the pkg.pr.new preview at `5775351fb`, which drops the Claude `plugin.json` `hooks` pointer so Claude Code no longer rejects the plugin with "Duplicate hooks file"
- ccd6a50: Start the auto-spawned daemon with a curated environment (`PATH`, `HOME`, `USER`, `LOGNAME`, `SHELL`, `TMPDIR`, `LANG`/`LC_*`, `XDG_*`, `CARGO_HOME`, `RUSTUP_HOME`, `SSL_CERT_*`, `*_proxy`, `CARGO_HAULER_*`) and the state directory as its cwd, so the first client's `RUSTFLAGS`, `CARGO_TARGET_DIR`, `RUSTC_WRAPPER`, or fd-based `MAKEFLAGS` no longer become the base of every other session's builds. `hauler exec` now relays SIGINT/SIGTERM: a brokered ticket is killed before the client exits `130`/`143`, a direct run's cargo process group is terminated instead of orphaned. A ticket that ends other than `done` prints `ticket cc-N <status>[: reason]` with `128 + signal` for signaled runs; a connection lost after the ack names the ticket (`hauler result cc-N`); a direct run prints its spawn error. The ack carries `waitEtaMs` and auto-background decides on queue wait plus runtime, shown as `wait ~Ns, run ~Ns`. A daemon that never accepted within 60 s goes straight to a direct run instead of a start attempt and a second cycle. `--cwd` is resolved against the caller. `CARGO_HAULER_CARGO_BIN` is read from the daemon's own environment. The PATH shim falls back to the real cargo when its hauler entry is gone and `install-shim` says to re-run `--force` after upgrades. Descriptor-based `MAKEFLAGS`/`MFLAGS`/`CARGO_MAKEFLAGS` jobservers are not transported. Heartbeats count from `started`; a failed or unconfirmed detach is reported; `hauler request --session` no longer holds the stop hook (matching `exec --bg`); `await`/`result`/`kill` fail fast on a daemon `error` reply (#55)
- ab91d88: Harden the daemon's socket and singleton-lock lifecycle. The daemon now listens on `daemon.sock.<pid>` and atomically renames it over `daemon.sock`, so a daemon that lost socket ownership no longer deletes its replacement's socket on the way out (previously both daemons died and their in-flight tickets were reaped as orphaned). `hauler daemon run` acquires the singleton lock before opening the ledger, running migrations, or draining the passthrough spool, so a losing instance touches neither; it re-checks a held lock about once a second for up to ~20 s instead of giving up on the first look, treats a lock written before the current boot as stale whatever its recorded pid, treats a pid it cannot signal (`EPERM`) as unknown rather than alive, and reclaims a stale lock atomically so two cold starters can no longer both start. A second Ctrl-C during shutdown is now swallowed instead of skipping finalizers and leaving the lock and socket behind (#50)
- d09b2fb: Harden the `tool/before` shell rewrite and its companions. The rewrite now leaves a command untouched when the pinned `bashjsast` parser cannot round-trip it — background `&`, the `time` keyword, `|&`, `coproc`, and a heredoc feeding a pipeline previously came back as a blocking, un-timed, or syntactically broken command. It no longer passes `--cwd`, so `cd crates/foo && cargo build` builds in `crates/foo`; it skips `command -v/-V cargo`, `type cargo`, and `which cargo`; wraps the unbrokered half of `hauler exec -- cargo build && cargo test`, `while ! cargo build`, and `rustup run <toolchain> -- cargo`. The `cargo clean` guard distinguishes a busy daemon (probe timeout → brokered so the lane serializes the clean) from an absent one (raw run). `tool/after` announces only background or detached tickets, never a foreground run the agent just watched. The stop route clamps `CARGO_HAULER_STOP_WAIT_MS` to the 2 h await ceiling, and `hook-state.json` is written atomically with per-session deny-counter pruning. (#56)
- df912ae: Honour `hauler kill` for a job parked at the admission gate or on the permit semaphore (it settles `killed` at once instead of blocking its lane until a permit frees), never fold a kill-requested queued job into a batch, clamp the queue ETA so an overrunning lane head no longer cancels queued work and count a head parked at the gate, and finish every settlement step (waiters, lane release, follower exits) even when a ledger write fails. Late attachers receive each replayed chunk exactly once, a follower whose leader exits during registration stays settled (attach/running ledger writes never reopen a terminal row), an early follower release can no longer surface as a `pump failed` cargo termination, and identity attach requires callers to agree on `mergeStderr`. When the shared jobserver FIFO is armed the daemon no longer sets `CARGO_BUILD_JOBS`; `CARGO_HAULER_JOBS_GRANT` applies only while the FIFO is unavailable. (#51, #52, #54)
- 3f70b60: Keep the daemon responsive and its storage bounded. Kache priors are refreshed without blocking the event loop: the `events.jsonl` tail is read asynchronously from a persisted byte offset and parsed in yielding slices, and the `index.db` aggregate is recomputed only when the file changes on disk. The ledger gains a startup retention pass — finished requests older than `CARGO_HAULER_LEDGER_RETENTION_DAYS` (default `30`) or beyond `CARGO_HAULER_LEDGER_MAX_ROWS` (default `50000`) are deleted with their transitions; `0` disables either limit — the attachment-savings backfill runs once per database (`PRAGMA user_version`) through the rowid index instead of on every open, and request/transition writes commit atomically. NDJSON client lines are capped at 16 MiB: an oversize line gets a `bad-message` error and the connection is closed. Numeric `CARGO_HAULER_*` overrides that do not parse or fall outside their range now log a warning and keep the default instead of silently disabling the arm; `0` or `off` still disables where documented, `CARGO_HAULER_MEM_PRESSURE_SOFT` must stay below `CARGO_HAULER_MEM_PRESSURE_HARD`, and `CARGO_HAULER_BATCH=false|off|no` disables batching like `0` (#57).
- dd778ab: Fold `cargo test` / `cargo nextest run` requests only when their test selection is identical — same `--test` targets, name filters, arguments after `--`, and nextest filterset — so a composite runs exactly what each participant asked for over the union of their packages (`cargo test -p a` + `cargo test -p b` → `cargo test -p a -p b --no-fail-fast`), never a foreign target or filter. When a composite fails, a folded participant inherits the failure only if it named every package the composite ran; otherwise it is requeued and runs alone instead of reporting another package's failure as its own. Unmodeled post-subcommand options that take a value (`-j`/`--jobs`, `--color`, `--message-format`, `-Z`, `--config`, and nextest's `--retries`, `--test-threads`, `-P`, …) now consume that value instead of recording it as a test-name filter (#53).

## 0.4.2

### Patch Changes

- 747f311: Remove `scripts/preview-dashboard.mjs`; preview the dashboard App through `agent-bundle dev` (the Workbench MCP page previews `ui://cargo-hauler/dashboard.html` over its bound session), and point the `hauler-dashboard` skill and README at it (#49)
- c5bfc4b: Add `hauler kill <ticket>` and the `hauler_kill` tool: a queued ticket is dropped, a running one has its cargo process group terminated (SIGTERM, then SIGKILL after `CARGO_HAULER_KILL_GRACE_MS`) and its lane freed, with riders settled by the daemon. The skill and session context now say to use it instead of killing cargo PIDs (#46). The `maxWaitMs` ceiling on `await` is reported in plain words rather than a raw validator payload (#47).
- 5f89f16: Merged-output runs (`cargo run 2>&1`, a shared terminal) strip cargo's captured color when the shared descriptor is not a color-capable TTY, matching direct cargo's `auto`, and the merge is honoured in passthrough runs too. `hauler await` / `hauler_await` subtract the time already spent fetching the ticket snapshot from the daemon wait, so the worst case stays under the render session. A failed run times its own intent for retries but no longer feeds the per-crate priors shared with other intents.

## 0.4.1

### Patch Changes

- 0861873: Align the plugin's remaining hand-synced surfaces with agent-bundle framework mode: the `hauler_status` tool and the dashboard MCP App route now read the widget URI from one imported `APP_RESOURCE_URI` const that the compiler resolves statically (no duplicated `ui://` literals), the App template is declared route-relative, and the unit-test pool takes its `agent-bundle/meta` alias from the framework's `agentBundleRstest` preset instead of a hand-written fixture. No runtime behavior changes.
- 889b430: A ticket settles once its cargo process exits even when a surviving descendant (an orphaned helper or a daemonized child) keeps the output pipe open: the daemon drains for one more second, then stops reading instead of waiting for pipe EOF indefinitely.

## 0.4.0

### Minor Changes

- 5c14816: The plugin layer is now a full agent-bundle application. Every MCP tool, hook, skill, and CLI command keeps its name and semantics; the daemon is unchanged.
  
  - Targets are `claude`, `codex`, `cursor`, and `portable` — one independently installable pack each under `artifact/<host>` (`output.distPath`) — instead of the composite `plugin` bundle. Install with `agent-bundle install <host> --from artifact/<host>` (Cursor `--mode local|marketplace`, `--replace` after a same-version rebuild) or, from an `npm pack`ed tarball, `npx cargo-hauler-install install <host>`; `agent-bundle prepack` gates the tarball, which now carries `artifact/` beside `dist/`. The project ships no installer of its own (cargo-hauler#25).
  - `src/layout.tsx`: the hauler shell around every rendered route — a daemon badge (what the request-start probe proved, with the permit/rider/queue summary), the route's document unchanged, a lineage footer naming the requesting conversation, and `_meta.hauler` (`route`, `surface`, `server`, `version`, `daemon`, `lineage`) on every MCP result.
  - `src/providers/hauler-daemon.ts` replaces `daemon-config`: one request-scoped daemon connection (`config`, `health`, `probedAt`) with typed unavailable states (`stopped: socket-missing | connection-refused`, `unresponsive: accept-timeout | answer-timeout | connection-closed`, `unreachable: open-failed` with the errno, `unprobed: event-surface`). It never fabricates status, and the probe budget bounds the socket accept as well as the answer (`requestOverSocket` gains an optional `openTimeoutMs`).
  - Components over pure view-models: `<TicketCard>`, `<TicketList>`, `<LaneBoard>`, `<AdmissionState>`, `<KacheStats>`, `<LogTail>`, `<BuildDiagnostics>` (an index of cargo `error[E…]`/`warning:` blocks — level/code/message/location — above the verbatim blocks), `<DashboardLink>`, `<DaemonBadge>`, `<LineageFooter>`, `Empty`/`Unavailable`/`Error` states; ticket guidance is one component per status.
  - Streaming: `hauler_await` / `hauler await` render the live ticket card and a progress node while the daemon-side wait blocks, then the settled ticket; `hauler_log` / `hauler log` stream a progress frame before the listing. `structuredContent` and `--json` are unchanged.
  - Attribution uses `request.lineage`: when a host publishes no session id (bare stdio MCP), the calling conversation becomes the ticket's session, so parallel agents' builds are attributable in the ledger, the dashboard, and `hauler status --session` (or the `hauler_status` tool's `session` field). `hauler_request` results carry `attribution` (`host`, `session`, `lineage`).
  - New `session/start` event route: each new Claude, Codex, or Cursor session receives the daemon state and the no-kill rule as additional context.
  - `hauler-dashboard` is a rendered skill (`SKILL.tsx`) computed from the tool and CLI spellings and the App resource URI it describes.
  - Tests: route-unit (layout, streaming, lineage, events), cli-dispatch, script-dispatch, mcp-in-memory against a live fixture broker, workbench-surface, and the packed-stdio contract matrix against `artifact/cursor`.
- f6765a1: Admission caps heavy leaders under low memory (#27): when `MemAvailable` is below `CARGO_HAULER_HEAVY_MEM_AVAILABLE_GB` (default 16 GiB, `off` to disable), at most `CARGO_HAULER_HEAVY_MAX_CONCURRENT` (default 1) release/perf/bench-profile or workspace-wide builds run at once. Held jobs report why in heartbeats, `hauler status`, and the dashboard (`admissionHold`, `heavy` in the load report). `cargo build -r` is now recognized as a release build for intent identity.
  
  Non-compiling cargo subcommands (`fmt`, `update`, `fetch`, `add`, `remove`, `generate-lockfile`, `vendor`, `new`, `init`, `info`, `uninstall`) run locally instead of queueing for a permit.
  
  The `hauler` script inside a host artifact forwards routed commands (`status`, `log`, `last`, `await`, `result`, `request`) to the bundled `bin/cargo-hauler.mjs`, so an installed plugin exposes the full CLI.
  
  agent-bundle re-pinned to preview `4edbd493b`; the framework now provides the `agent-bundle/meta` test alias, script discovery alongside `bin` claims, and same-version installer replacement, so the local workarounds for those are removed.

### Patch Changes

- c6b9bc6: The admission line in `hauler status` and the `hauler_status` tool counts permit holders only; requests riding a shared build are reported separately instead of inflating "running" past the permit cap.
- dce27a7: Bump `agent-bundle` and `@agent-bundle/runtime` to the pkg.pr.new preview of agent-bundle `main` commit `886b192` (`0.0.0-preview-886b192`, previously `4edbd493b`).
- 58a09e1: Paths are canonicalized through their nearest existing ancestor, so a target directory spells the same before and after cargo creates it (macOS `/var` → `/private/var`); lane keys and coverage attachment no longer diverge between a leader and its followers (#35, #36).
- dbfc3c0: `hauler exec` (and the PATH shim) no longer converts a synchronous request to a background ticket on a cold-start estimate: only a measured ETA (`etaSource` `ewma` or `kache`, now carried on the ack) can exceed the host shell cap, so a compile error on a fresh crate reaches the caller instead of exit 0 (#37). When a request is auto-backgrounded it exits `75` (`EX_TEMPFAIL`) and says so; explicit `--bg` keeps exit 0. The shim borrows `CARGO_HAULER_HOST`'s cap when exported, failed runs feed the estimate history, and the client now waits for the daemon's `detach-result` before hanging up so a still-queued ticket is not killed as abandoned.
  
  `hauler await` / `hauler_await` bound one call to 55 s (`maxWaitMs` ceiling), under the framework's 60 s render session; longer waits used to fail with `Agent render elapsed time exceeds 60000ms` and lose the result (#32). Guidance, the skill, and the README say to call again.
  
  Brokered output preserves the program's stdout/stderr write order when the caller's two descriptors are one file (`cargo run 2>&1`, a shared terminal): the client sends `mergeStderr` and the daemon runs the child with stderr on the stdout pipe. Demultiplexed `build`/`check`/`clippy` runs keep separate channels (#38).
- f242bd2: Forward caller environment variables through brokered Cargo executions while filtering cargo-hauler internal settings.
- 929d96c: Publish releases through Changesets and npm trusted publishing with provenance.
- fd3cf6d: When `CARGO_HAULER_STATE_DIR` is too deep for a unix socket path (103 bytes on macOS, 107 on Linux), the daemon socket moves to `$XDG_RUNTIME_DIR` / `$TMPDIR` as `cargo-hauler-<digest>.sock` instead of silently failing to bind; every client resolving the same state dir agrees on the endpoint.

Notable changes per release, newest first. Versions are the `version` field in
`package.json`; releases before 0.3.0 are described by their commit messages in
`git log`.

## Unreleased

- `hauler exec` (shim and hook rewrites) forwards the caller's whole
  environment to the daemon-spawned Cargo, withholding only `CARGO_HAULER_*`
  and legacy `CARGO_CONDUCTOR_*`. Previously only `CARGO_*`, `RUST*`, the C
  toolchain variables, and color variables travelled, so a `build.rs` or test
  reading any other variable built differently through the broker than
  directly (#29). Request identity still digests the build-relevant subset
  only, so coalescing is unchanged.

## 0.3.5 (2026-09-03)

Repository hygiene release; no behavior change.

- Daemon, client, hook, and surface code drop narrating comments and unsafe
  casts in favor of runtime narrowing and the direct Effect v4 recovery
  idioms (`catchCauseIf`, `orElseSucceed`, `ignore`).
- Test suites share scoped temp-dir, database, and ledger helpers from
  `tests/harness.ts`; fixture directories are prefixed `cargo-hauler-it-`.
- README, `docs/install.md`, and `AGENTS.md` match the framework-mode layout
  and 0.3.x behavior (install commands, admission controls, `unresponsive`
  state, hook rewrites, the Cursor manifest-hook limitation).
- `scripts/preview-dashboard.mjs` reads from `dist/bin/cargo-hauler.js`
  again; the finished v3→v4 migration skill, unreferenced media, and a dead
  `tsconfig.views.json` are removed. `CHANGELOG.md` is added and shipped.

## 0.3.4 (2026-09-03)

0.3.3 was not released; 0.3.2 went straight to 0.3.4.

- Shell hooks rewrite the Cargo escape hatches agents actually use:
  `env -u X VAR=y cargo …`, `timeout 600 cargo …`,
  `rustup run <toolchain> cargo …`, and `stdbuf -oL cargo …` now go through the
  broker. Only `rustup run` counts as a wrapper; other `rustup` subcommands
  are left alone.
- The `exec` hot path no longer prints Node's SQLite `ExperimentalWarning` or
  the removed `CARGO_CONDUCTOR_STATE_DIR` reminder into agents' tool output;
  the reminder is kept for hand-run commands only.
- Test suites run with a per-worker `CARGO_HAULER_STATE_DIR`, so hook
  recorders and probes cannot write fixtures into the developer's live ledger.

## 0.3.2 (2026-09-03)

- Daemon status gains a third state, `unresponsive`: a socket that exists but
  does not answer within the status budget (raised from 2 s to 5 s) is no
  longer reported as `stopped` while jobs are in flight. The summary, MCP
  document, and dashboard all render it.
- MCP and CLI documents show shim-originated Cargo by basename instead of the
  full real-cargo path, matching the dashboard.

## 0.3.1 (2026-09-03)

- The PATH shim embeds the `~/.cargo/bin/cargo` link itself rather than its
  canonical rustup proxy target; rustup dispatches on `argv[0]`, so the old
  shim ran `rustup check …`.
- `install-shim` refuses unknown flags instead of installing on `--help`.
- Packed-stdio contract test: the built `artifact/plugin` MCP entry runs as a
  real process against an in-process broker and every tool passes the
  framework matrix.
- Effect tests run through `effect-rstest` (`it.live` / `it.effect` own the
  per-test scope; daemons, ledgers, and temp trees are `acquireRelease`
  resources).
- Status summary pluralizes kache entries.

## 0.3.0 (2026-09-03)

- Plugin surface rebuilt on agent-bundle framework mode. Filesystem routes
  replace the `defineOperation` / application / server layer:
  `src/mcp/hauler/{tools,apps}`, `src/events`, `src/cli`, `src/providers`, and
  a `src/scripts/hauler.ts` process entry for `exec`, `install-shim`, and
  `daemon`. Shared document components render the same Agent Documents on MCP
  and CLI; `hauler_await` streams progress. Route-unit, CLI-dispatch, and
  in-memory MCP suites run through `agent-bundle/rstest` without an artifact
  build.
- agent-bundle re-pinned to pkg.pr.new preview `105c65d8f`
  (`@agent-bundle/rsc-runtime` became `@agent-bundle/runtime`). The project
  moved from npm to pnpm because npm 12 refuses pkg.pr.new tarball URLs by
  default. Skills moved to `src/skills`.
- A daemon that takes longer than the 2 s open timeout to accept a socket is a
  `ControlTimeout`, not an unreachable daemon: `exec` retries the connection
  for up to 60 s before falling back to an unbrokered run, and status no
  longer reports a loaded daemon as stopped.
- Cursor installs use the artifact's own `install.mjs`; the hand-rolled
  installer is gone.
- Docs: queued heartbeats carry lane context, delayed-wait flags, and quiet-run
  hints; the host-tuning document was removed.
