cargo-hauler 0.6.18 → 0.7.0

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 (40) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +39 -5
  3. package/dist/.claude-plugin/marketplace.json +1 -1
  4. package/dist/.claude-plugin/plugin.json +1 -1
  5. package/dist/.codex-plugin/plugin.json +1 -1
  6. package/dist/.cursor-plugin/plugin.json +1 -1
  7. package/dist/INSTALL.md +1 -1
  8. package/dist/README.md +39 -5
  9. package/dist/agent-bundle.compile-evidence.json +1 -1
  10. package/dist/agent-bundle.manifest.json +1 -1
  11. package/dist/agent-bundle.package-compile-evidence.json +1 -1
  12. package/dist/bin/cargo-hauler-flight.mjs +446 -396
  13. package/dist/bin/cargo-hauler.mjs +1025 -683
  14. package/dist/bin/hauler.js +1230 -758
  15. package/dist/hooks/event-route-session-start.claude.mjs +2 -2
  16. package/dist/hooks/event-route-session-start.codex.mjs +2 -2
  17. package/dist/hooks/event-route-session-start.cursor.mjs +2 -2
  18. package/dist/hooks/event-route-stop.claude.mjs +2 -2
  19. package/dist/hooks/event-route-stop.codex.mjs +2 -2
  20. package/dist/hooks/event-route-stop.cursor.mjs +2 -2
  21. package/dist/hooks/event-route-tool-after.claude.execute.mjs +2 -2
  22. package/dist/hooks/event-route-tool-after.claude.mjs +12 -13
  23. package/dist/hooks/event-route-tool-after.codex.execute.mjs +2 -2
  24. package/dist/hooks/event-route-tool-after.codex.mjs +12 -13
  25. package/dist/hooks/event-route-tool-after.cursor.execute.mjs +2 -2
  26. package/dist/hooks/event-route-tool-after.cursor.mjs +12 -13
  27. package/dist/hooks/event-route-tool-before.claude.execute.mjs +2 -2
  28. package/dist/hooks/event-route-tool-before.codex.execute.mjs +2 -2
  29. package/dist/hooks/event-route-tool-before.cursor.execute.mjs +2 -2
  30. package/dist/hooks/hooks-flight.mjs +49 -43
  31. package/dist/install.mjs +1 -1
  32. package/dist/mcp/mcp-hauler-2b8242f9-flight.mjs +468 -419
  33. package/dist/mcp/mcp-hauler-2b8242f9.mjs +126 -76
  34. package/dist/mcp-apps/dashboard.html +7 -7
  35. package/dist/package.json +1 -2
  36. package/dist/plugin.json +1 -1
  37. package/dist/scripts/hauler.mjs +1230 -758
  38. package/dist/skills/cargo-hauler/SKILL.md +7 -0
  39. package/dist/skills/hauler-dashboard/SKILL.md +1 -1
  40. package/package.json +1 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,53 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 80939ab: A `hauler exec` whose daemon connection drops after the ticket was accepted now
8
+ reattaches instead of giving up. The client prints `connection to daemon lost;
9
+ reattaching to ticket cc-N…`, reconnects (starting the daemon again if it is
10
+ gone, a few attempts one second apart), and sends the new `reattach` message;
11
+ the daemon rebinds the ticket, replays the output the client had not yet
12
+ received, and streams the rest, so the caller's exit code is cargo's as if the
13
+ connection had held. Output the replay buffer no longer holds is announced as
14
+ `N bytes of output missed while reconnecting; full log: <path>`, never
15
+ invented. A ticket that finished in the meantime yields its exit code and log
16
+ path.
17
+
18
+ The daemon no longer kills a queued ticket the moment its submitter
19
+ disconnects: it keeps its queue position (and may start) for
20
+ `CARGO_HAULER_REATTACH_GRACE_MS` (default 30000; `0` restores the immediate
21
+ kill) and is killed as `killed while queued: submitter disconnected and did
22
+ not reattach within 30s` only if nobody reattaches. A running ticket continues
23
+ as before, marked orphaned; a reattach clears the flag.
24
+
25
+ When the ticket cannot be reattached — it never ran cargo and was killed at
26
+ the daemon's shutdown or `orphaned by daemon restart`, the daemon does not
27
+ know it, the daemon predates this release and answers the message with
28
+ `bad-message`, or no daemon answered within the budget — the client fails
29
+ closed with `brokered run aborted: daemon connection lost; ticket cc-N
30
+ <reason>` and the new exit code `69` (`EX_UNAVAILABLE`), distinct from cargo's
31
+ `1` and from `2`/`75`/`130`/`143`. It no longer prints `ticket cc-N continues
32
+ — hauler result cc-N` and exits `1` while the daemon records `killed while
33
+ queued`. `--bg` and auto-backgrounded tickets are detached, not owned, and
34
+ are unchanged. (#187)
35
+ - 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.
36
+
37
+ ### Patch Changes
38
+
39
+ - f84e901: `hauler exec --allow-shared-target` (and `CARGO_HAULER_ALLOW_SHARED_TARGET=1`
40
+ in the caller's environment) now reaches the daemon: the server dropped the
41
+ request field, so only the daemon-side setting could admit a shared target dir.
42
+ The shared-target warning has one wording everywhere — the daemon's refusal
43
+ and ack line, the lane board, and the dashboard's lane cell (other roots, full
44
+ text on hover) — and `hauler request` shows the daemon's warning in its summary
45
+ when the daemon admitted a shared target. The `hauler status` summary string
46
+ no longer carries extra warning lines the document never rendered; the lane
47
+ board is where the warning shows.
48
+ Detection no longer re-resolves already-canonical lane paths on every submit
49
+ and status poll.
50
+
3
51
  ## 0.6.18
4
52
 
5
53
  ### Patch Changes
package/README.md CHANGED
@@ -77,8 +77,8 @@ The CLI is `hauler` on PATH from `npm i -g cargo-hauler`. Never run
77
77
 
78
78
  | Command | Behavior |
79
79
  | --- | --- |
80
- | `hauler exec [--session ID] [--host HOST] [--cwd DIR] [--bg] [--after TICKET[,TICKET…]] -- <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. `--after` (repeatable or comma-separated) keeps the request queued until every named ticket has finished; it fails with `prerequisite cc-N <status>` if one of them fails or is killed, and an unknown ticket is rejected as a bad intent. Exits with cargo's code; `130`/`143` after a SIGINT/SIGTERM (the ticket is killed first); `75` when auto-backgrounded. |
81
- | `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. Rows are bounded summaries: no row carries an output tail; a running row carries `outputPreview`, the last 8 lines (at most 512 bytes) of its live output, cut at a line boundary, and every other row has `outputPreview: null`. Read a ticket's whole tail with `hauler result`. |
80
+ | `hauler exec [--session ID] [--host HOST] [--cwd DIR] [--bg] [--after TICKET[,TICKET…]] [--allow-shared-target] -- <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. `--after` (repeatable or comma-separated) keeps the request queued until every named ticket has finished; it fails with `prerequisite cc-N <status>` if one of them fails or is killed, and an unknown ticket is rejected as a bad intent. `--allow-shared-target` accepts the stale-artifact risk described below and prints a warning. Exits with cargo's code; `130`/`143` after a SIGINT/SIGTERM (the ticket is killed first); `75` when auto-backgrounded; `69` when the daemon connection was lost and the ticket could not be reattached (see below). |
81
+ | `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. Lanes sharing one external target directory across workspace roots carry `sharedTargetWith` and render a warning naming the target and roots. Rows are bounded summaries: no row carries an output tail; a running row carries `outputPreview`, the last 8 lines (at most 512 bytes) of its live output, cut at a line boundary, and every other row has `outputPreview: null`. Read a ticket's whole tail with `hauler result`. |
82
82
  | `hauler log [--limit N]` | Recent requests from the ledger, as the same bounded summary rows. |
83
83
  | `hauler last` | The most recent request, as a detail record (from the daemon while it is running, otherwise from the ledger) — its output tail included. |
84
84
  | `hauler await <ticket> [--max-wait-ms N]` | Long-poll until the ticket finishes or the wait expires (default 30 s, ceiling 2 h per call — the daemon's await ceiling; call again to keep waiting). A host with its own per-call deadline still bounds one call: Codex stops a tool call at `tool_timeout_sec` (60 s unless raised). |
@@ -187,6 +187,14 @@ arms defer admission under load. Attached requests (riders) do not hold
187
187
  permits; the admission meter counts permit holders and reports riders
188
188
  separately.
189
189
 
190
+ Different workspace roots must not share a target directory outside the
191
+ requesting workspace by default. Cargo's `-C metadata` hash is relative to the
192
+ workspace root, so same-layout git worktrees can write identical artifact
193
+ filenames there; whichever worktree compiled last may then be treated as fresh
194
+ and run by another. This is a stale-binary collision, not a kache miss. Use a
195
+ target directory per worktree, or opt in with `--allow-shared-target` /
196
+ `CARGO_HAULER_ALLOW_SHARED_TARGET=1` when that risk is intentional.
197
+
190
198
  Within a lane, the daemon can reduce work in three ways:
191
199
 
192
200
  1. **Identity attachment:** a byte-identical request attaches to an in-flight
@@ -327,6 +335,7 @@ own `-j` flag or `CARGO_BUILD_JOBS` always wins over both.
327
335
  | --- | --- |
328
336
  | Work sharing | Identical requests attach, covered checks and compile-only `test --no-run` requests attach, and compatible queued compile or test requests fold. |
329
337
  | Lane isolation | A workspace-root and target-directory pair is serialized independently from other lanes. |
338
+ | Shared target safety | If different workspace roots use the same target directory outside the requesting workspace, the daemon refuses the later request unless explicitly allowed and status flags both lanes. |
330
339
  | 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. |
331
340
  | 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. |
332
341
  | Scheduling | Per-phase EWMA estimates (compile vs execute), optional kache priors, fan-out, dependency topology, recent edits, and request age determine lane order; `--after cc-N` holds a request until the named tickets settle. |
@@ -381,9 +390,32 @@ answer, and exits `130` or `143`; in a direct run it terminates the cargo
381
390
  process group the same way. A ticket that ends other than `done` is reported
382
391
  on stderr as `ticket cc-N <status>[ (signal)][: reason]`, and its exit code is
383
392
  cargo's, `128 + signal` for a signaled run, or `1` when the daemon could not
384
- start cargo at all. If the connection drops after the ticket was accepted, the
385
- client prints `connection to daemon lost; ticket cc-N continues — hauler
386
- result cc-N` and exits `1`; the daemon finishes the ticket on its own.
393
+ start cargo at all.
394
+
395
+ If the connection drops after the ticket was accepted (a daemon restart or
396
+ replacement, a dropped socket), the client keeps the ticket rather than the
397
+ connection: it prints `connection to daemon lost; reattaching to ticket
398
+ cc-N…`, reconnects — starting the daemon again if it is gone, a few attempts
399
+ one second apart — and sends `reattach`. The daemon meanwhile holds a ticket
400
+ whose submitter vanished: one still queued keeps its place for
401
+ `CARGO_HAULER_REATTACH_GRACE_MS` (30 s; it may start in the meantime) and is
402
+ killed as `killed while queued: submitter disconnected and did not reattach
403
+ within 30s` only if nobody comes back; one already running continues, marked
404
+ orphaned as before. A reattach rebinds the ticket to the new connection,
405
+ clears the orphan flag, replays the output the client had not yet received
406
+ from the replay buffer, and streams the rest, so the exit code is cargo's as
407
+ if nothing had happened; output the buffer no longer holds is announced as
408
+ `N bytes of output missed while reconnecting; full log: <path>` rather than
409
+ invented. A ticket that finished in the meantime yields its exit code and log
410
+ path. When the ticket cannot be reattached — it never ran cargo and was
411
+ killed at the daemon's shutdown, `orphaned by daemon restart`, unknown to the
412
+ daemon that answered, the daemon predates the message, or no daemon answered
413
+ within the budget — the client exits `69` (`EX_UNAVAILABLE`) with `brokered
414
+ run aborted: daemon connection lost; ticket cc-N <reason>`; it never claims
415
+ the build ran. `CARGO_HAULER_REATTACH_GRACE_MS=0` restores the earlier policy
416
+ of killing a queued ticket the moment its connection closes. `--bg` and
417
+ auto-backgrounded tickets are detached, not owned, and are untouched by any
418
+ of this.
387
419
 
388
420
  A deadlocked test binary holds its lane for ever at 0% CPU with nothing on
389
421
  stdout, and neither the estimate overrun nor the output silence alone can
@@ -606,6 +638,7 @@ Per-host notes and hook timeouts are in [docs/install.md](docs/install.md).
606
638
  | `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. |
607
639
  | `CARGO_HAULER_MAX_CONCURRENT` | cores ÷ 8, clamped to 5–16 | Global admission permits for Cargo processes across all lanes; an integer >= 1. |
608
640
  | `CARGO_HAULER_OVERLAP_EXECUTION` | `1` | Hand a lane to its next request once a `test`/`nextest`/`bench`/`run` leader reports its build finished, overlapping the next compile with the leader's execution phase. `0` keeps a lane strictly one process at a time. |
641
+ | `CARGO_HAULER_ALLOW_SHARED_TARGET` | `0` | Allow different workspace roots to use one external target directory. The daemon still warns and status flags the lanes because Cargo artifacts can collide. Set on the request as `1`, or in the daemon environment to allow all requests. |
609
642
  | `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. |
610
643
  | `CARGO_HAULER_JOBSERVER` | `auto` | Machine-wide fifo jobserver for daemon-spawned cargo: `auto` arms it only when the host `make` is 4.4+ (or absent) because older makes reject `--jobserver-auth=fifo:` in build scripts; `fifo` forces it on, `off` disables it (per-run `CARGO_BUILD_JOBS` grants apply instead). |
611
644
  | `CARGO_HAULER_LOAD_THRESHOLD` | Disabled | Per-core one-minute load threshold for deferring new admissions. |
@@ -625,6 +658,7 @@ Per-host notes and hook timeouts are in [docs/install.md](docs/install.md).
625
658
  | `CARGO_HAULER_STALL_ESTIMATE_FACTOR` | `3` | A running ticket becomes a stall candidate once its elapsed time exceeds this multiple of its estimate. |
626
659
  | `CARGO_HAULER_STALL_IDLE_MS` | `600000` | Window with no process-tree CPU time and no output after which a stall candidate is flagged `stalled`; `0` or `off` disables stall detection. |
627
660
  | `CARGO_HAULER_STALL_AUTO_KILL` | Enabled | Kill a stalled ticket automatically once the connection that submitted it has disconnected. `0`, `false`, `off`, or `no` only flags it. |
661
+ | `CARGO_HAULER_REATTACH_GRACE_MS` | `30000` | How long a queued ticket keeps its place after its submitting connection dropped, waiting for the client to `reattach`; then it is killed as abandoned. `0` kills it the moment the connection closes. |
628
662
  | `CARGO_HAULER_STOP_WAIT_MS` | `30000` | Maximum wait for one stop-hook invocation; values above the 7200000 ms await ceiling are clamped. |
629
663
  | `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. |
630
664
  | `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. Pruned rows take their `tickets/<ticket>.log` files with them. |
@@ -1 +1 @@
1
- {"description":"Coalesce, schedule, and stream cargo so concurrent agent sessions share compiles instead of fighting locks.","name":"cargo-hauler-marketplace","owner":{"name":"cargo-hauler"},"plugins":[{"description":"Coalesce, schedule, and stream cargo so concurrent agent sessions share compiles instead of fighting locks.","name":"cargo-hauler","source":"./","version":"0.6.18"}]}
1
+ {"description":"Coalesce, schedule, and stream cargo so concurrent agent sessions share compiles instead of fighting locks.","name":"cargo-hauler-marketplace","owner":{"name":"cargo-hauler"},"plugins":[{"description":"Coalesce, schedule, and stream cargo so concurrent agent sessions share compiles instead of fighting locks.","name":"cargo-hauler","source":"./","version":"0.7.0"}]}
@@ -1 +1 @@
1
- {"author":{"name":"cargo-hauler"},"description":"Coalesce, schedule, and stream cargo so concurrent agent sessions share compiles instead of fighting locks.","name":"cargo-hauler","version":"0.6.18"}
1
+ {"author":{"name":"cargo-hauler"},"description":"Coalesce, schedule, and stream cargo so concurrent agent sessions share compiles instead of fighting locks.","name":"cargo-hauler","version":"0.7.0"}
@@ -1 +1 @@
1
- {"author":{"name":"cargo-hauler"},"description":"Coalesce, schedule, and stream cargo so concurrent agent sessions share compiles instead of fighting locks.","hooks":"./.codex-plugin/hooks.json","interface":{"capabilities":["mcp","hooks","skills"],"category":"Productivity","defaultPrompt":["Help me use cargo-hauler."],"developerName":"cargo-hauler","displayName":"cargo-hauler","longDescription":"Coalesce, schedule, and stream cargo so concurrent agent sessions share compiles instead of fighting locks.","shortDescription":"Coalesce, schedule, and stream cargo so concurrent agent sessions share compiles instead of fighting locks."},"mcpServers":"./.codex-plugin/mcp.json","name":"cargo-hauler","skills":"./skills/","version":"0.6.18"}
1
+ {"author":{"name":"cargo-hauler"},"description":"Coalesce, schedule, and stream cargo so concurrent agent sessions share compiles instead of fighting locks.","hooks":"./.codex-plugin/hooks.json","interface":{"capabilities":["mcp","hooks","skills"],"category":"Productivity","defaultPrompt":["Help me use cargo-hauler."],"developerName":"cargo-hauler","displayName":"cargo-hauler","longDescription":"Coalesce, schedule, and stream cargo so concurrent agent sessions share compiles instead of fighting locks.","shortDescription":"Coalesce, schedule, and stream cargo so concurrent agent sessions share compiles instead of fighting locks."},"mcpServers":"./.codex-plugin/mcp.json","name":"cargo-hauler","skills":"./skills/","version":"0.7.0"}
@@ -1 +1 @@
1
- {"description":"Coalesce, schedule, and stream cargo so concurrent agent sessions share compiles instead of fighting locks.","displayName":"cargo-hauler","hooks":"./.cursor-plugin/hooks.json","mcpServers":"./.cursor-plugin/mcp.json","name":"cargo-hauler","skills":"./skills/","version":"0.6.18"}
1
+ {"description":"Coalesce, schedule, and stream cargo so concurrent agent sessions share compiles instead of fighting locks.","displayName":"cargo-hauler","hooks":"./.cursor-plugin/hooks.json","mcpServers":"./.cursor-plugin/mcp.json","name":"cargo-hauler","skills":"./skills/","version":"0.7.0"}
package/dist/INSTALL.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Coalesce, schedule, and stream cargo so concurrent agent sessions share compiles instead of fighting locks.
4
4
 
5
- Version: `0.6.18`
5
+ Version: `0.7.0`
6
6
 
7
7
  Run these commands from this bundle directory. The bundle is self-contained: every command below is
8
8
  a host command or the bundled installer, and nothing requires the `agent-bundle` CLI. Where that CLI is
package/dist/README.md CHANGED
@@ -77,8 +77,8 @@ The CLI is `hauler` on PATH from `npm i -g cargo-hauler`. Never run
77
77
 
78
78
  | Command | Behavior |
79
79
  | --- | --- |
80
- | `hauler exec [--session ID] [--host HOST] [--cwd DIR] [--bg] [--after TICKET[,TICKET…]] -- <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. `--after` (repeatable or comma-separated) keeps the request queued until every named ticket has finished; it fails with `prerequisite cc-N <status>` if one of them fails or is killed, and an unknown ticket is rejected as a bad intent. Exits with cargo's code; `130`/`143` after a SIGINT/SIGTERM (the ticket is killed first); `75` when auto-backgrounded. |
81
- | `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. Rows are bounded summaries: no row carries an output tail; a running row carries `outputPreview`, the last 8 lines (at most 512 bytes) of its live output, cut at a line boundary, and every other row has `outputPreview: null`. Read a ticket's whole tail with `hauler result`. |
80
+ | `hauler exec [--session ID] [--host HOST] [--cwd DIR] [--bg] [--after TICKET[,TICKET…]] [--allow-shared-target] -- <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. `--after` (repeatable or comma-separated) keeps the request queued until every named ticket has finished; it fails with `prerequisite cc-N <status>` if one of them fails or is killed, and an unknown ticket is rejected as a bad intent. `--allow-shared-target` accepts the stale-artifact risk described below and prints a warning. Exits with cargo's code; `130`/`143` after a SIGINT/SIGTERM (the ticket is killed first); `75` when auto-backgrounded; `69` when the daemon connection was lost and the ticket could not be reattached (see below). |
81
+ | `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. Lanes sharing one external target directory across workspace roots carry `sharedTargetWith` and render a warning naming the target and roots. Rows are bounded summaries: no row carries an output tail; a running row carries `outputPreview`, the last 8 lines (at most 512 bytes) of its live output, cut at a line boundary, and every other row has `outputPreview: null`. Read a ticket's whole tail with `hauler result`. |
82
82
  | `hauler log [--limit N]` | Recent requests from the ledger, as the same bounded summary rows. |
83
83
  | `hauler last` | The most recent request, as a detail record (from the daemon while it is running, otherwise from the ledger) — its output tail included. |
84
84
  | `hauler await <ticket> [--max-wait-ms N]` | Long-poll until the ticket finishes or the wait expires (default 30 s, ceiling 2 h per call — the daemon's await ceiling; call again to keep waiting). A host with its own per-call deadline still bounds one call: Codex stops a tool call at `tool_timeout_sec` (60 s unless raised). |
@@ -187,6 +187,14 @@ arms defer admission under load. Attached requests (riders) do not hold
187
187
  permits; the admission meter counts permit holders and reports riders
188
188
  separately.
189
189
 
190
+ Different workspace roots must not share a target directory outside the
191
+ requesting workspace by default. Cargo's `-C metadata` hash is relative to the
192
+ workspace root, so same-layout git worktrees can write identical artifact
193
+ filenames there; whichever worktree compiled last may then be treated as fresh
194
+ and run by another. This is a stale-binary collision, not a kache miss. Use a
195
+ target directory per worktree, or opt in with `--allow-shared-target` /
196
+ `CARGO_HAULER_ALLOW_SHARED_TARGET=1` when that risk is intentional.
197
+
190
198
  Within a lane, the daemon can reduce work in three ways:
191
199
 
192
200
  1. **Identity attachment:** a byte-identical request attaches to an in-flight
@@ -327,6 +335,7 @@ own `-j` flag or `CARGO_BUILD_JOBS` always wins over both.
327
335
  | --- | --- |
328
336
  | Work sharing | Identical requests attach, covered checks and compile-only `test --no-run` requests attach, and compatible queued compile or test requests fold. |
329
337
  | Lane isolation | A workspace-root and target-directory pair is serialized independently from other lanes. |
338
+ | Shared target safety | If different workspace roots use the same target directory outside the requesting workspace, the daemon refuses the later request unless explicitly allowed and status flags both lanes. |
330
339
  | 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. |
331
340
  | 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. |
332
341
  | Scheduling | Per-phase EWMA estimates (compile vs execute), optional kache priors, fan-out, dependency topology, recent edits, and request age determine lane order; `--after cc-N` holds a request until the named tickets settle. |
@@ -381,9 +390,32 @@ answer, and exits `130` or `143`; in a direct run it terminates the cargo
381
390
  process group the same way. A ticket that ends other than `done` is reported
382
391
  on stderr as `ticket cc-N <status>[ (signal)][: reason]`, and its exit code is
383
392
  cargo's, `128 + signal` for a signaled run, or `1` when the daemon could not
384
- start cargo at all. If the connection drops after the ticket was accepted, the
385
- client prints `connection to daemon lost; ticket cc-N continues — hauler
386
- result cc-N` and exits `1`; the daemon finishes the ticket on its own.
393
+ start cargo at all.
394
+
395
+ If the connection drops after the ticket was accepted (a daemon restart or
396
+ replacement, a dropped socket), the client keeps the ticket rather than the
397
+ connection: it prints `connection to daemon lost; reattaching to ticket
398
+ cc-N…`, reconnects — starting the daemon again if it is gone, a few attempts
399
+ one second apart — and sends `reattach`. The daemon meanwhile holds a ticket
400
+ whose submitter vanished: one still queued keeps its place for
401
+ `CARGO_HAULER_REATTACH_GRACE_MS` (30 s; it may start in the meantime) and is
402
+ killed as `killed while queued: submitter disconnected and did not reattach
403
+ within 30s` only if nobody comes back; one already running continues, marked
404
+ orphaned as before. A reattach rebinds the ticket to the new connection,
405
+ clears the orphan flag, replays the output the client had not yet received
406
+ from the replay buffer, and streams the rest, so the exit code is cargo's as
407
+ if nothing had happened; output the buffer no longer holds is announced as
408
+ `N bytes of output missed while reconnecting; full log: <path>` rather than
409
+ invented. A ticket that finished in the meantime yields its exit code and log
410
+ path. When the ticket cannot be reattached — it never ran cargo and was
411
+ killed at the daemon's shutdown, `orphaned by daemon restart`, unknown to the
412
+ daemon that answered, the daemon predates the message, or no daemon answered
413
+ within the budget — the client exits `69` (`EX_UNAVAILABLE`) with `brokered
414
+ run aborted: daemon connection lost; ticket cc-N <reason>`; it never claims
415
+ the build ran. `CARGO_HAULER_REATTACH_GRACE_MS=0` restores the earlier policy
416
+ of killing a queued ticket the moment its connection closes. `--bg` and
417
+ auto-backgrounded tickets are detached, not owned, and are untouched by any
418
+ of this.
387
419
 
388
420
  A deadlocked test binary holds its lane for ever at 0% CPU with nothing on
389
421
  stdout, and neither the estimate overrun nor the output silence alone can
@@ -606,6 +638,7 @@ Per-host notes and hook timeouts are in [docs/install.md](docs/install.md).
606
638
  | `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. |
607
639
  | `CARGO_HAULER_MAX_CONCURRENT` | cores ÷ 8, clamped to 5–16 | Global admission permits for Cargo processes across all lanes; an integer >= 1. |
608
640
  | `CARGO_HAULER_OVERLAP_EXECUTION` | `1` | Hand a lane to its next request once a `test`/`nextest`/`bench`/`run` leader reports its build finished, overlapping the next compile with the leader's execution phase. `0` keeps a lane strictly one process at a time. |
641
+ | `CARGO_HAULER_ALLOW_SHARED_TARGET` | `0` | Allow different workspace roots to use one external target directory. The daemon still warns and status flags the lanes because Cargo artifacts can collide. Set on the request as `1`, or in the daemon environment to allow all requests. |
609
642
  | `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. |
610
643
  | `CARGO_HAULER_JOBSERVER` | `auto` | Machine-wide fifo jobserver for daemon-spawned cargo: `auto` arms it only when the host `make` is 4.4+ (or absent) because older makes reject `--jobserver-auth=fifo:` in build scripts; `fifo` forces it on, `off` disables it (per-run `CARGO_BUILD_JOBS` grants apply instead). |
611
644
  | `CARGO_HAULER_LOAD_THRESHOLD` | Disabled | Per-core one-minute load threshold for deferring new admissions. |
@@ -625,6 +658,7 @@ Per-host notes and hook timeouts are in [docs/install.md](docs/install.md).
625
658
  | `CARGO_HAULER_STALL_ESTIMATE_FACTOR` | `3` | A running ticket becomes a stall candidate once its elapsed time exceeds this multiple of its estimate. |
626
659
  | `CARGO_HAULER_STALL_IDLE_MS` | `600000` | Window with no process-tree CPU time and no output after which a stall candidate is flagged `stalled`; `0` or `off` disables stall detection. |
627
660
  | `CARGO_HAULER_STALL_AUTO_KILL` | Enabled | Kill a stalled ticket automatically once the connection that submitted it has disconnected. `0`, `false`, `off`, or `no` only flags it. |
661
+ | `CARGO_HAULER_REATTACH_GRACE_MS` | `30000` | How long a queued ticket keeps its place after its submitting connection dropped, waiting for the client to `reattach`; then it is killed as abandoned. `0` kills it the moment the connection closes. |
628
662
  | `CARGO_HAULER_STOP_WAIT_MS` | `30000` | Maximum wait for one stop-hook invocation; values above the 7200000 ms await ceiling are clamped. |
629
663
  | `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. |
630
664
  | `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. Pruned rows take their `tickets/<ticket>.log` files with them. |