cargo-hauler 0.4.1 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +119 -39
  3. package/artifact/agent-bundle.manifest.json +1 -1
  4. package/artifact/claude/.claude-plugin/marketplace.json +1 -1
  5. package/artifact/claude/.claude-plugin/plugin.json +1 -1
  6. package/artifact/claude/INSTALL.md +1 -1
  7. package/artifact/claude/bin/cargo-hauler-flight.mjs +758 -212
  8. package/artifact/claude/bin/cargo-hauler.mjs +1625 -673
  9. package/artifact/claude/hooks/event-route-session-start.mjs +2 -3
  10. package/artifact/claude/hooks/event-route-stop.mjs +2 -3
  11. package/artifact/claude/hooks/event-route-tool-after.mjs +2 -3
  12. package/artifact/claude/hooks/event-route-tool-before.mjs +2 -3
  13. package/artifact/claude/hooks/hooks-flight.mjs +653 -196
  14. package/artifact/claude/mcp/mcp-hauler-2b8242f9-flight.mjs +1087 -350
  15. package/artifact/claude/mcp/mcp-hauler-2b8242f9.mjs +867 -241
  16. package/artifact/claude/mcp-apps/dashboard.html +1 -1
  17. package/artifact/claude/scripts/hauler.mjs +1702 -699
  18. package/artifact/claude/skills/cargo-hauler/SKILL.md +15 -7
  19. package/artifact/claude/skills/hauler-dashboard/SKILL.md +1 -8
  20. package/artifact/codex/.codex-plugin/plugin.json +1 -1
  21. package/artifact/codex/INSTALL.md +1 -1
  22. package/artifact/codex/bin/cargo-hauler-flight.mjs +758 -212
  23. package/artifact/codex/bin/cargo-hauler.mjs +1625 -673
  24. package/artifact/codex/hooks/event-route-session-start.mjs +2 -3
  25. package/artifact/codex/hooks/event-route-stop.mjs +2 -3
  26. package/artifact/codex/hooks/event-route-tool-after.mjs +2 -3
  27. package/artifact/codex/hooks/event-route-tool-before.mjs +2 -3
  28. package/artifact/codex/hooks/hooks-flight.mjs +653 -196
  29. package/artifact/codex/mcp/mcp-hauler-2b8242f9-flight.mjs +1087 -350
  30. package/artifact/codex/mcp/mcp-hauler-2b8242f9.mjs +867 -241
  31. package/artifact/codex/mcp-apps/dashboard.html +1 -1
  32. package/artifact/codex/scripts/hauler.mjs +1702 -699
  33. package/artifact/codex/skills/cargo-hauler/SKILL.md +15 -7
  34. package/artifact/codex/skills/hauler-dashboard/SKILL.md +1 -8
  35. package/artifact/cursor/.cursor-plugin/plugin.json +1 -1
  36. package/artifact/cursor/INSTALL.md +1 -1
  37. package/artifact/cursor/bin/cargo-hauler-flight.mjs +758 -212
  38. package/artifact/cursor/bin/cargo-hauler.mjs +1625 -673
  39. package/artifact/cursor/hooks/event-route-session-start.mjs +2 -3
  40. package/artifact/cursor/hooks/event-route-stop.mjs +2 -3
  41. package/artifact/cursor/hooks/event-route-tool-after.mjs +2 -3
  42. package/artifact/cursor/hooks/event-route-tool-before.mjs +2 -3
  43. package/artifact/cursor/hooks/hooks-flight.mjs +653 -196
  44. package/artifact/cursor/install.mjs +1 -1
  45. package/artifact/cursor/mcp/mcp-hauler-2b8242f9-flight.mjs +1087 -350
  46. package/artifact/cursor/mcp/mcp-hauler-2b8242f9.mjs +867 -241
  47. package/artifact/cursor/mcp-apps/dashboard.html +1 -1
  48. package/artifact/cursor/scripts/hauler.mjs +1702 -699
  49. package/artifact/cursor/skills/cargo-hauler/SKILL.md +15 -7
  50. package/artifact/cursor/skills/hauler-dashboard/SKILL.md +1 -8
  51. package/artifact/portable/INSTALL.md +1 -1
  52. package/artifact/portable/bin/cargo-hauler-flight.mjs +758 -212
  53. package/artifact/portable/bin/cargo-hauler.mjs +1625 -673
  54. package/artifact/portable/install.mjs +1 -1
  55. package/artifact/portable/mcp/mcp-hauler-2b8242f9-flight.mjs +767 -212
  56. package/artifact/portable/mcp/mcp-hauler-2b8242f9.mjs +866 -239
  57. package/artifact/portable/mcp-apps/dashboard.html +1 -1
  58. package/artifact/portable/plugin.json +1 -1
  59. package/artifact/portable/scripts/hauler.mjs +1702 -699
  60. package/artifact/portable/skills/cargo-hauler/SKILL.md +15 -7
  61. package/artifact/portable/skills/hauler-dashboard/SKILL.md +1 -8
  62. package/dist/bin/cargo-hauler-flight.mjs +758 -212
  63. package/dist/bin/cargo-hauler.js +1625 -673
  64. package/dist/bin/hauler.js +1702 -699
  65. package/package.json +3 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.3
4
+
5
+ ### Patch Changes
6
+
7
+ - 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"
8
+ - 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)
9
+ - 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)
10
+ - 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)
11
+ - 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)
12
+ - 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).
13
+ - 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).
14
+
15
+ ## 0.4.2
16
+
17
+ ### Patch Changes
18
+
19
+ - 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)
20
+ - 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).
21
+ - 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.
22
+
3
23
  ## 0.4.1
4
24
 
5
25
  ### Patch Changes
package/README.md CHANGED
@@ -136,11 +136,12 @@ the same filter as its `session` field). Results carry
136
136
  | `tool:hauler/hauler_last` · `cli:last` | most recent request | `LastDocument` |
137
137
  | `tool:hauler/hauler_await` · `cli:await` | long-poll a ticket (≤ 2 h) | `AwaitStream` → `AwaitDocument` |
138
138
  | `tool:hauler/hauler_result` · `cli:result` | one ticket, live tail while running | `ResultDocument` |
139
+ | `tool:hauler/hauler_kill` · `cli:kill` | stop a queued or running ticket | `KillDocument` |
139
140
  | `tool:hauler/hauler_request` · `cli:request` | submit a background request | `RequestDocument` |
140
141
  | `cli:daemon` | `run` / `start` / `stop` / `status` | plain JSON, exit code from the result |
141
142
  | `event:session/start` | new session | daemon state and the no-kill rule as context |
142
- | `event:tool/before` | shell tool about to run | rewrites `cargo …` to `hauler exec --session … --host … -- cargo …`; denies `cargo clean` during in-flight builds |
143
- | `event:tool/after` | shell tool finished | injects finished-ticket results once per session |
143
+ | `event:tool/before` | shell tool about to run | rewrites `cargo …` to `hauler exec --session … --host … -- cargo …`; denies `cargo clean` during in-flight builds, brokers it while the daemon is too busy to answer |
144
+ | `event:tool/after` | shell tool finished | injects finished background-ticket results once per session |
144
145
  | `event:stop` | agent stopping | holds the stop while a foreground ticket is pending (bounded, re-deniable) |
145
146
 
146
147
  ### Skills
@@ -215,19 +216,20 @@ executable beside it (`dist/bin/cargo-hauler.js` in the package,
215
216
 
216
217
  | Command | Behavior |
217
218
  | --- | --- |
218
- | `hauler exec [--session ID] [--host HOST] [--cwd DIR] [--bg] -- <cargo …>` | Submit Cargo through the daemon and stream output; hooks rewrite commands to this form. |
219
+ | `hauler exec [--session ID] [--host HOST] [--cwd DIR] [--bg] -- <cargo …>` | Submit Cargo through the daemon and stream output; hooks rewrite commands to this form. A relative `--cwd` is resolved against the caller's directory. Exits with cargo's code; `130`/`143` after a SIGINT/SIGTERM (the ticket is killed first); `75` when auto-backgrounded. |
219
220
  | `hauler status [--limit N] [--cwd DIR] [--session ID] [--lane KEY] [--ticket ID …] [--status S …] [--command-contains TEXT]` | Queue, active runs, lanes, admission, kache, optionally filtered. |
220
221
  | `hauler log [--limit N]` | Recent requests from the ledger. |
221
222
  | `hauler last` | The most recent request. |
222
223
  | `hauler await <ticket> [--max-wait-ms N]` | Long-poll until the ticket finishes or the wait expires (default 30 s, ceiling 55 s per call — the rendered-route budget; call again to keep waiting). |
223
224
  | `hauler result <ticket>` | A stored ticket; running tickets include a live output tail. |
225
+ | `hauler kill <ticket>` | Stop a ticket: drop it from the queue or SIGTERM (then SIGKILL) its cargo process group, freeing the lane. Riders return to their lane or fail with it. |
224
226
  | `hauler request [--session ID] [--host HOST] [--cwd DIR] -- <cargo …>` | Submit a background request and return its ticket. |
225
227
  | `hauler daemon <run\|start\|stop\|status>` | Manage the daemon lifecycle. |
226
228
  | `hauler install-shim [--dir DIR] [--real-cargo PATH] [--force]` | Install the optional PATH shim. |
227
229
 
228
230
  The `hauler` MCP server projects the same operations as `hauler_status`,
229
- `hauler_log`, `hauler_last`, `hauler_await`, `hauler_result`, and
230
- `hauler_request`, with the same filters as the CLI.
231
+ `hauler_log`, `hauler_last`, `hauler_await`, `hauler_result`, `hauler_kill`,
232
+ and `hauler_request`, with the same filters as the CLI.
231
233
 
232
234
  ## Testing
233
235
 
@@ -262,9 +264,23 @@ The hook parses the shell command and rewrites each Cargo invocation to
262
264
  `hauler exec --session … --host … -- cargo …`. It recognizes `cargo` behind an
263
265
  absolute path (`~/.cargo/bin/cargo`), and behind the wrappers agents actually
264
266
  use: `env -u VAR X=y cargo …`, `timeout 600 cargo …`,
265
- `rustup run <toolchain> cargo …`, `stdbuf`, `nice`, `ionice`, `nohup`,
266
- `time`, `strace`, `sudo`, `xargs`, `command`, `exec`, and `builtin`. Other
267
- `rustup` subcommands and already-wrapped commands are left alone.
267
+ `rustup run <toolchain> [--] cargo …`, `stdbuf`, `nice`, `ionice`, `nohup`,
268
+ `/usr/bin/time`, `strace`, `sudo`, `xargs`, `command`, `exec`, `builtin`,
269
+ and a negated test (`while ! cargo build; do …`). Other `rustup`
270
+ subcommands, lookups (`command -v cargo`, `type cargo`, `which cargo`), and
271
+ already-wrapped invocations are left alone; in a partially wrapped list
272
+ (`hauler exec -- cargo build && cargo test`) only the unwrapped half is
273
+ rewritten. The rewrite never passes `--cwd`: the command runs in the same
274
+ shell, so `hauler exec` inherits the working directory and
275
+ `cd crates/foo && cargo build` builds in `crates/foo`.
276
+
277
+ Before rewriting, the hook checks that the parser can reproduce the original
278
+ command token for token. Constructs the pinned parser cannot round-trip —
279
+ a background `&` (`nohup cargo build … &`, `cargo build & pid=$!`), the
280
+ `time` keyword, `|&`, `coproc`, a heredoc that feeds a pipeline or is
281
+ followed by another statement, `elif`, and `function name { … }` — are left
282
+ untouched and run as plain Cargo rather than risk emitting a changed
283
+ command.
268
284
 
269
285
  A lane is keyed by workspace root and resolved target directory. It runs one
270
286
  job at a time. Different lanes may run concurrently after acquiring one of the
@@ -286,8 +302,17 @@ Each admitted leader starts one Cargo process. Identity, coverage, and folded
286
302
  batch requests share that process and receive its streamed output. A failed
287
303
  stronger compile does not satisfy a coverage or compile-batch attachment; the
288
304
  attached request returns to its lane unless its required compilation units were
289
- already observed as successful. Folded tests share the composite process,
290
- output, and exit code.
305
+ already observed as successful. Folded tests share the composite process and
306
+ output. `cargo test` / `cargo nextest run` requests fold only when their test
307
+ selection is identical — the same `--test` targets, name filters, arguments
308
+ after `--`, and nextest filterset — so only the package set differs:
309
+ `cargo test -p a` and `cargo test -p b` become
310
+ `cargo test -p a -p b --no-fail-fast`. On success every participant shares
311
+ the exit. When the composite fails, a participant inherits that failure only
312
+ if it named every package the composite ran; otherwise the failing tests may
313
+ belong to another participant's package, so it is requeued and runs alone
314
+ (cargo's test output does not attribute failures to packages). The leader
315
+ keeps the composite exit, as compile-batch leaders do.
291
316
 
292
317
  Brokered output keeps cargo's stdout and stderr as separate channels. When the
293
318
  caller's own stdout and stderr are the same open file (`cargo run 2>&1`, a
@@ -322,18 +347,22 @@ heartbeats. Non-compiling cargo subcommands (`fmt`, `update`, `fetch`, `add`,
322
347
  `remove`, `generate-lockfile`, `vendor`, `new`, `init`, `info`, `uninstall`)
323
348
  run locally instead of queueing for a permit.
324
349
 
325
- The per-run `CARGO_BUILD_JOBS` grant defaults to the available cores divided
326
- across the configured permit count, with a floor of four jobs. Separately, the
327
- daemon arms one GNU make jobserver FIFO with `cores - 1` tokens when it
350
+ The daemon arms one GNU make jobserver FIFO with `cores - 1` tokens when it
328
351
  acquires the singleton lock and passes it to every Cargo it spawns through
329
352
  `MAKEFLAGS`, so concurrent lanes share one global rustc parallelism budget.
353
+ While the FIFO is armed no `CARGO_BUILD_JOBS` is injected, because Cargo only
354
+ joins an inherited jobserver when `-j`/`build.jobs` is unset. The per-run
355
+ `CARGO_BUILD_JOBS` grant — the available cores divided across the configured
356
+ permit count, with a floor of four jobs — is the fallback for a daemon that
357
+ could not arm the FIFO (no `mkfifo`, unwritable state directory). A caller's
358
+ own `-j` flag or `CARGO_BUILD_JOBS` always wins over both.
330
359
 
331
360
  | Capability | Behavior |
332
361
  | --- | --- |
333
362
  | Work sharing | Identical requests attach, covered checks attach, and compatible queued compile or test requests fold. |
334
363
  | Lane isolation | A workspace-root and target-directory pair is serialized independently from other lanes. |
335
364
  | Admission | Per-core load, Linux CPU PSI, Linux memory PSI and `MemAvailable`, macOS VM pressure, configured thresholds, and the global permit cap control new starts. |
336
- | Parallelism | A per-run `CARGO_BUILD_JOBS` grant plus one daemon-owned jobserver FIFO shared by every spawned Cargo. |
365
+ | Parallelism | One daemon-owned jobserver FIFO shared by every spawned Cargo; a per-run `CARGO_BUILD_JOBS` grant only when the FIFO could not be armed. |
337
366
  | Scheduling | EWMA estimates, optional kache priors, fan-out, dependency topology, recent edits, and request age determine lane order. |
338
367
  | Persistence | Tickets, output tails, timings, outcomes, and savings are stored in SQLite. |
339
368
  | Caller output and status | Output streams to attached callers; late callers receive buffered replay. After 30 seconds without output, the client emits a progress heartbeat every 15 seconds with lane queue position, the lane-head ticket, and an aggregate wait ETA. |
@@ -349,19 +378,37 @@ immediately. A synchronous request also switches to background mode when a
349
378
  *measured* estimate — EWMA history or kache priors, never the cold-start
350
379
  default — exceeds the host's shell-tool cap (nine minutes for Claude, ten for
351
380
  Codex, fourteen for Cursor; the PATH shim uses `CARGO_HAULER_HOST` when it is
352
- exported, otherwise the Claude cap). That conversion exits `75`
353
- (`EX_TEMPFAIL`) with the ticket on stderr, so `cargo build && …` chains and
354
- scripts cannot mistake "submitted" for "built"; explicit `--bg` keeps exit
381
+ exported, otherwise the Claude cap). The estimate that is compared is the
382
+ whole wait: the work queued ahead in the lane plus the job's own runtime,
383
+ which the queued line reports as `wait ~Ns, run ~Ns`. That conversion exits
384
+ `75` (`EX_TEMPFAIL`) with the ticket on stderr, so `cargo build && …` chains
385
+ and scripts cannot mistake "submitted" for "built"; explicit `--bg` keeps exit
355
386
  `0`. Failed runs feed the estimate history too, so a broken build is not
356
387
  re-estimated cold on every retry.
357
388
 
358
- The `tool/after` route checks session tickets and, on the first tool call
359
- after a ticket finishes, adds its result to the agent context. For foreground
360
- tickets, the `stop` route waits for the lower of the remaining estimate and
361
- `CARGO_HAULER_STOP_WAIT_MS`; if the ticket finishes it denies the stop and
362
- returns the result, otherwise it denies with status and ETA. `stopHookActive`
363
- and an eight-denial cap per ticket prevent a repeated stop loop; background
364
- tickets never hold a stop. Codex 0.147.0 stop-hold behaviour is verified in
389
+ A foreground `hauler exec` that receives SIGINT or SIGTERM (Ctrl-C, or a
390
+ `timeout N …` wrapper) asks the daemon to kill its ticket, waits for the
391
+ answer, and exits `130` or `143`; in a direct run it terminates the cargo
392
+ process group the same way. A ticket that ends other than `done` is reported
393
+ on stderr as `ticket cc-N <status>[ (signal)][: reason]`, and its exit code is
394
+ cargo's, `128 + signal` for a signaled run, or `1` when the daemon could not
395
+ start cargo at all. If the connection drops after the ticket was accepted, the
396
+ client prints `connection to daemon lost; ticket cc-N continues — hauler
397
+ result cc-N` and exits `1`; the daemon finishes the ticket on its own.
398
+
399
+ The `tool/after` route checks the session's background tickets — `--bg`,
400
+ `hauler_request`, and synchronous requests the client converted to a ticket
401
+ — and, on the first tool call after one finishes, adds its result to the
402
+ agent context. A foreground ticket streamed its exit to the shell the agent
403
+ just watched, so it is never re-announced. For foreground tickets, the
404
+ `stop` route waits for the lower of the remaining estimate and
405
+ `CARGO_HAULER_STOP_WAIT_MS` (clamped to the daemon's two-hour await
406
+ ceiling); if the ticket finishes it denies the stop and returns the result,
407
+ otherwise it denies with status and ETA. `stopHookActive` and an
408
+ eight-denial cap per ticket prevent a repeated stop loop; the per-ticket
409
+ counters live in `hook-state.json`, written atomically and pruned once a
410
+ session's tickets are no longer pending. `--bg` tickets never hold a stop.
411
+ Codex 0.147.0 stop-hold behaviour is verified in
365
412
  [docs/codex-hooks.md](docs/codex-hooks.md).
366
413
 
367
414
  ### PATH shim
@@ -373,7 +420,11 @@ When the daemon starts Cargo it sets `CARGO_HAULER_INSIDE=1`, and the shim then
373
420
  invokes the embedded Cargo directly, so the daemon's own Cargo never returns
374
421
  through the broker. The shim is POSIX-only; its directory must appear before
375
422
  rustup's Cargo directory on `PATH`; replacing an existing destination requires
376
- `--force`.
423
+ `--force`. The embedded `hauler` entry lives in a versioned plugin directory:
424
+ when that file no longer exists (an upgrade replaced the directory), the shim
425
+ runs the embedded Cargo directly instead of failing, and `install-shim` says
426
+ so — re-run `hauler install-shim --force` after such an upgrade to route
427
+ scripted Cargo through the broker again.
377
428
 
378
429
  ### Caller environment
379
430
 
@@ -386,10 +437,24 @@ Cargo, so `FOO=bar cargo build` reaches `build.rs`, `env!()`, `cargo run`, and
386
437
  for coalescing is digested from the build-relevant subset only (`CARGO_*`,
387
438
  `RUST*`, `CC`/`CXX`/`AR`/`CFLAGS`/`CXXFLAGS`/`LDFLAGS` with target-suffixed
388
439
  forms, and `PKG_CONFIG_PATH`); pass knobs a `build.rs` reads through
389
- `--config 'env.FOO="bar"'` when they must also split identity.
440
+ `--config 'env.FOO="bar"'` when they must also split identity. One value is
441
+ filtered rather than forwarded: a `MAKEFLAGS`, `MFLAGS`, or `CARGO_MAKEFLAGS`
442
+ carrying a descriptor-based jobserver (`--jobserver-auth=R,W`,
443
+ `--jobserver-fds=R,W`) names file descriptors that exist only in the caller,
444
+ so it is dropped and the daemon's shared FIFO jobserver applies; a
445
+ `fifo:PATH` jobserver travels as-is.
390
446
  `hauler request` and `hauler_request` submit without a caller environment;
391
447
  their Cargo processes run with the daemon's environment.
392
448
 
449
+ The daemon's own environment is deliberately small. When a client starts it,
450
+ the daemon receives only `PATH`, `HOME`, `USER`, `LOGNAME`, `SHELL`, `TMPDIR`,
451
+ `LANG` and `LC_*`, `XDG_*`, `CARGO_HOME`, `RUSTUP_HOME`, `SSL_CERT_*`, the
452
+ `*_proxy` variables, and every `CARGO_HAULER_*` setting, with the state
453
+ directory as its working directory. The starting shell's `RUSTFLAGS`,
454
+ `CARGO_TARGET_DIR`, `RUSTC_WRAPPER`, `CARGO_BUILD_*`, `MAKEFLAGS`, `CC`, and
455
+ similar build knobs are not inherited, so they cannot become the silent base of
456
+ every other session's builds.
457
+
393
458
  ### Kache integration
394
459
 
395
460
  When [kache](https://github.com/ScriptedAlchemy/kache) is available,
@@ -405,27 +470,32 @@ is reported as unavailable and never rejects a request.
405
470
  | Variable | Default | Meaning |
406
471
  | --- | --- | --- |
407
472
  | `CARGO_HAULER_STATE_DIR` | Per-user cache directory | Unix socket or Windows named pipe source, SQLite ledger, daemon log, pid lock, `hook-state.json`, and `hook-events.jsonl`. No legacy alias. |
408
- | `CARGO_HAULER_CARGO_BIN` | `$CARGO_HOME/bin/cargo` | Cargo binary for daemon-started work; bare `cargo` is the last fallback. Never resolved through `PATH`. |
409
- | `CARGO_HAULER_MAX_CONCURRENT` | `5` | Global admission permits for Cargo processes across all lanes. |
410
- | `CARGO_HAULER_JOBS_GRANT` | `max(4, cores / max concurrent)` | `CARGO_BUILD_JOBS` added to each Cargo process; `0` disables injection. |
473
+ | `CARGO_HAULER_CARGO_BIN` | `$CARGO_HOME/bin/cargo` | Cargo binary for daemon-started work; bare `cargo` is the last fallback. Never resolved through `PATH`. Read from the daemon's own environment (export it where the daemon starts, or before `hauler daemon start`); clients do not forward it. |
474
+ | `CARGO_HAULER_MAX_CONCURRENT` | `5` | Global admission permits for Cargo processes across all lanes; an integer >= 1. |
475
+ | `CARGO_HAULER_JOBS_GRANT` | `max(4, cores / max concurrent)` | `CARGO_BUILD_JOBS` added to each Cargo process only while the shared jobserver FIFO is not armed; an armed daemon injects `MAKEFLAGS` instead and leaves `CARGO_BUILD_JOBS` unset. `0` disables injection. |
411
476
  | `CARGO_HAULER_LOAD_THRESHOLD` | Disabled | Per-core one-minute load threshold for deferring new admissions. |
412
477
  | `CARGO_HAULER_LOAD_MIN` | `2` | Active Cargo processes below which load, CPU PSI, and soft memory pressure do not defer admission. |
413
- | `CARGO_HAULER_CPU_PRESSURE_THRESHOLD` | `75` | Linux CPU PSI `some avg10` percentage for deferring new admissions; `0` disables. |
414
- | `CARGO_HAULER_MEM_PRESSURE_SOFT` | `10` (Linux) | Memory PSI `full avg10` percentage for soft deferral; `0` disables. |
415
- | `CARGO_HAULER_MEM_PRESSURE_HARD` | `20` (Linux) | Memory PSI `full avg10` percentage for hard deferral, confirmed by `full avg60` at half the value; `0` disables. |
416
- | `CARGO_HAULER_MEM_AVAILABLE_MIN_GB` | `8` (Linux) | `MemAvailable` floor in GiB for hard deferral; `0` disables. |
417
- | `CARGO_HAULER_MEM_PRESSURE_LEVEL` | `2` (macOS) | Kernel VM pressure level that starts soft deferral (`2` warn, `4` critical). |
478
+ | `CARGO_HAULER_CPU_PRESSURE_THRESHOLD` | `75` | Linux CPU PSI `some avg10` percentage for deferring new admissions; `0` or `off` disables. |
479
+ | `CARGO_HAULER_MEM_PRESSURE_SOFT` | `10` (Linux) | Memory PSI `full avg10` percentage for soft deferral; `0` or `off` disables. Must stay below the hard threshold, otherwise both revert to their defaults. |
480
+ | `CARGO_HAULER_MEM_PRESSURE_HARD` | `20` (Linux) | Memory PSI `full avg10` percentage for hard deferral, confirmed by `full avg60` at half the value; `0` or `off` disables. |
481
+ | `CARGO_HAULER_MEM_AVAILABLE_MIN_GB` | `8` (Linux) | `MemAvailable` floor in GiB for hard deferral; `0` or `off` disables. |
482
+ | `CARGO_HAULER_MEM_PRESSURE_LEVEL` | `2` (macOS) | Kernel VM pressure level that starts soft deferral (`2` warn, `4` critical); `0` or `off` disables. |
418
483
  | `CARGO_HAULER_HEAVY_MEM_AVAILABLE_GB` | `16` (Linux) | `MemAvailable` in GiB below which concurrent heavy leaders (release/perf/bench profiles, workspace-wide runs) are capped; `0` or `off` disables the cap. |
419
484
  | `CARGO_HAULER_HEAVY_MAX_CONCURRENT` | `1` | Heavy leaders admitted at once while the cap is active. |
420
485
  | `CARGO_HAULER_REPLAY_BUFFER_BYTES` | `4194304` | Leader output retained in memory for late-attacher replay. |
421
486
  | `CARGO_HAULER_KACHE_INDEX` | kache's configured store | kache index for per-crate timing priors; an empty string disables it. |
422
- | `CARGO_HAULER_BATCH` | Enabled | `0` disables the batch composer. |
487
+ | `CARGO_HAULER_BATCH` | Enabled | `0`, `false`, `off`, or `no` disables the batch composer. |
423
488
  | `CARGO_HAULER_BATCH_WINDOW_MS` | `150` | Delay applied to a batchable lane head so nearby requests can fold; `0` disables. |
424
489
  | `CARGO_HAULER_KILL_GRACE_MS` | `8000` | Time between SIGTERM and SIGKILL when the daemon stops a Cargo process. |
425
- | `CARGO_HAULER_STOP_WAIT_MS` | `30000` | Maximum wait for one stop-hook invocation. |
490
+ | `CARGO_HAULER_STOP_WAIT_MS` | `30000` | Maximum wait for one stop-hook invocation; values above the 7200000 ms await ceiling are clamped. |
491
+ | `CARGO_HAULER_LEDGER_RETENTION_DAYS` | `30` | Finished ledger rows older than this many days are deleted when the daemon starts; `0` disables the age limit. |
492
+ | `CARGO_HAULER_LEDGER_MAX_ROWS` | `50000` | Total ledger rows beyond which the oldest finished rows are deleted when the daemon starts; `0` disables the row cap. |
426
493
  | `CARGO_HAULER_LOG_LEVEL` | `Info` | Daemon log level. |
427
494
  | `CARGO_HAULER_HOST`, `CARGO_HAULER_SESSION` | Unset | Default `--host` and `--session` attribution for `hauler exec`; the PATH shim also borrows `CARGO_HAULER_HOST`'s shell cap for auto-background. |
428
495
 
496
+ A numeric value that does not parse or falls outside its range is reported
497
+ as a warning (daemon log, or stderr for hand-run commands) and the default
498
+ applies; only `0` or `off` disables an arm that documents that contract.
429
499
  Each `CARGO_HAULER_*` tuning value takes precedence over its retained legacy
430
500
  `CARGO_CONDUCTOR_*` alias; `CARGO_CONDUCTOR_STATE_DIR` is ignored and hand-run
431
501
  commands warn when it is still exported (see
@@ -443,7 +513,8 @@ unset, the daemon reads kache's configured local store from
443
513
  command through, and a client that cannot reach the daemon makes one
444
514
  auto-start attempt and then invokes Cargo directly. A daemon that is alive
445
515
  but too loaded to accept within 2 seconds is not treated as absent: `exec`
446
- retries for up to 60 seconds before falling back to a direct run.
516
+ retries for up to 60 seconds, then runs Cargo directly without a start
517
+ attempt or a second retry cycle.
447
518
  - The plugin's own documents never fail open: `hauler_result` and
448
519
  `hauler_await` fail loudly when the daemon is unreachable instead of
449
520
  reporting a ticket as not found; `hauler_status`, `hauler_log`, and
@@ -452,6 +523,11 @@ unset, the daemon reads kache's configured local store from
452
523
  - Test sharing uses identity attachment or batch folding, never coverage.
453
524
  Folded `test` and `nextest` requests receive the composite output and exit
454
525
  code, so a failure may come from another package in the batch.
526
+ - The `cargo clean` guard probes the daemon for 250 ms. Active work denies
527
+ the clean; an idle daemon brokers it; a daemon that accepts but does not
528
+ answer in time is busy, so the clean is brokered and the lane serializes
529
+ it behind the builds it would otherwise race; only a socket nobody listens
530
+ on (`ECONNREFUSED`, `ENOENT`) lets a raw `cargo clean` run.
455
531
  - Hook rewrites, policy denials such as `cargo clean` during an active build,
456
532
  and malformed requests are recorded (`hook-events.jsonl`; a failed ledger
457
533
  row).
@@ -467,12 +543,16 @@ pnpm run build # artifact/{claude,codex,cursor,portable} and dist/bin
467
543
  pnpm run inspect # per-host component accounting
468
544
  pnpm run doctor # installed copies versus the artifact
469
545
  pnpm run check # the gate
470
- node scripts/preview-dashboard.mjs --port 4941 # dashboard outside an MCP host
471
546
  ```
472
547
 
548
+ To see the dashboard outside an MCP host, run `pnpm run dev` and open the
549
+ Workbench's MCP page: it binds a session to the generated `hauler` server and
550
+ previews the `ui://cargo-hauler/dashboard.html` App over that session, so the
551
+ data is the daemon's own. The repository ships no preview harness of its own.
552
+
473
553
  agent-bundle does not yet have an npm release; this repository pins the
474
554
  [pkg.pr.new](https://pkg.pr.new) preview of main commit
475
- [`4edbd493b`](https://github.com/ScriptedAlchemy/agent-bundle/commit/4edbd493b)
555
+ [`5775351fb`](https://github.com/ScriptedAlchemy/agent-bundle/commit/5775351fbc1c82e2861a9d69cad78adb086d052d)
476
556
  for both `agent-bundle` and `@agent-bundle/runtime`. `inspect` reports the
477
557
  `agent` component kind as unavailable on every host (agent-bundle G5
478
558
  deferral); this plugin defines no agents. Two framework limitations observed