Skip to content

meta: v2 baseline release + zccache patch removal + #782 Phase A + #497 follow-ups #500

Closed

Description

@zackees

⚠ Not a /clud-fix target. Coordination tracker for the next batch of v2 broker work. Each row is /clud-pr-driveable on main. This meta exists to anchor a single /goal on a focused next-milestone chunk: drop zccache's [patch.crates-io] git+rev pin (the most visible cleanup) + finish #782 Phase A + close out the #497 trait-shape follow-ups.

Background

The broker-v2 baseline is live on running-process/main — slices 1–9 from #488 shipped. zccache consumes the new surface via a [patch.crates-io] running-process = { git+rev } pin while the migration runs (4 zccache PRs merged: #778, #779, #780, #781, #789, #790, #791, #795). The #782 meta drives the consumer-side burn-down (13 slices remaining). #497 added the BrokeredBackend trait scaffold and explicitly deferred Option B + trybuild + launcher-budget test.

This meta wraps a single coherent milestone — small enough to drive to completion in a focused goal, large enough to deliver visible cleanup ("zccache stops carrying a git+rev pin").

Done When

All sub-items checked AND:

  1. running-process has a published crates.io release exposing the full v2 surface: protocol_v2, client_v2, names_v2, brokered_backend, http_endpoint_registry, get_http_endpoint_dispatch, broker_http_port, broker_http_server, plus the running-process-broker-v2 binary.
  2. zccache's [patch.crates-io] running-process = { git+rev } is removed; zccache compiles + tests against the published version like any other downstream consumer.
  3. zccache#782 Phase A (slices 15–17) is fully ticked.
  4. feat(broker-v2): formalize daemon startup contract — declare fast-bind vs lazy-init, broker-enforced budget #497's three deferred follow-ups have either shipped or been explicitly punted via a follow-up issue with a clear reason.

Queue

Each row is its own /clud-pr slice. Order is dependency-led — every row below depends on the rows above.

Track 1 — Ship the v2 surface (running-process)

  • Slice 30 — running-process release on crates.io. (PR chore(release): bump 4.4.0 -> 4.5.0 (slice 30 of #500) #501; Auto Release fires post-merge) Bump workspace version (4.4.0 → 4.5.0 or whatever the version-check script wants), exercise uv run --module ci.version_check locally, then push a tagged release through the Auto Release workflow (.github/workflows/auto-release.yml). Manually confirm the release on crates.io exposes the v2 modules listed above.

Track 2 — Drop the pin (zccache)

Track 3 — Finish #782 Phase A

Track 4 — #497 follow-ups

How to drive this

  • Per slice: /clud-pr <this meta> with a scope sentence referencing the specific slice number above.
  • Per merge: /clud-pr-merge <PR url> or the explicit "merge it - we can fix issues later" admin-merge cadence from the established pattern.
  • Coordination updates: tick the checkbox + add the PR number after each merge so the queue stays current.
  • Docker precheck convention: every slice that touches zccache runs MSYS_NO_PATHCONV=1 docker run --rm -v "<repo>:/work" -w /work -v zccache-slice11-cargo-cache:/usr/local/cargo/registry -v zccache-slice11-target:/work/target rust:1.94.1 bash -c "cargo test -p zccache --lib ipc::broker_v2" before pushing. running-process slices run uv run ci/dev_docker.py cargo test --features client --lib <module>.

Related

Update 2026-06-20: vendoring shipped

Activity

  1. zackees commented on Jun 20, 2026

    @zackees
    OwnerAuthor

    Found a release-pipeline regression while running Phase 0 of this meta (the 'check existing builder failures' step in the goal directive).

    Auto Release workflow run 27858611833 on the 4.5.0 bump (#501) failed at the Build ConPTY sidecars and manifest step:

    curl: (22) The requested URL returned error: 404
    

    Root cause: WINDOWS_CONPTY_VERSION.txt pins Microsoft.Windows.Console.ConPTY 1.24.260402001, which NuGet no longer serves (404). Latest stable version available is 1.24.260512001. Publish crates.io and Publish PyPI were SKIPPED so 4.5.0 never published — the next release attempt has to bump the patch level (4.5.1) since the 4.5.0 git tag exists.

    Fixing as Slice 30b (Phase 0 follow-up):

    1. Update WINDOWS_CONPTY_VERSION.txt to 1.24.260512001.
    2. Bump workspace version 4.5.0 → 4.5.1 (the 4.5.0 tag already exists from the failed release).
    3. Merge → triggers Auto Release → 4.5.1 publishes to crates.io + PyPI.
    4. Slice 31 (Track 2) then bumps zccache to running-process = "4.5.1" and removes the [patch.crates-io] git+rev pin.
  2. zackees commented on Jun 20, 2026

    @zackees
    OwnerAuthor

    Slice 30b update — release still failing, different cause:

    PR #503 (NuGet pin 1.24.260512001 + bump to 4.5.1) merged. Second Auto Release run 27859121907 now fails at a different step:

    tar: OpenConsole.exe: Cannot stat: No such file or directory
    

    The newer NuGet package (1.24.260512001) has a different on-disk layout than the workflow expects. Specifically the OpenConsole.exe binary is not at the path the workflow's tar -C "$src" -cf - conpty.dll OpenConsole.exe ... invocation expects.

    This is now a release-workflow debugging exercise on the ConPTY sidecar contract (#447), not v2-broker work. Recommended next steps:

    1. Download the new NuGet package locally, inspect its layout: curl -fsSL "https://www.nuget.org/api/v2/package/Microsoft.Windows.Console.ConPTY/1.24.260512001" -o /tmp/conpty.nupkg && unzip -l /tmp/conpty.nupkg | grep -i opencons.
    2. Either update the workflow's per-arch source path globs to find OpenConsole.exe at its new location, OR pin to a NuGet version that still bundles it at the historical path (none of the currently-served versions may qualify; see nuget v3-flatcontainer index).
    3. Once the sidecar build is fixed, bump version 4.5.1 → 4.5.2 (the 4.5.1 git tag exists from this failed attempt) and re-merge.

    Filing as Slice 30c — release workflow ConPTY sidecar packaging update — to track separately from the v2 broker work in this meta. Track 2 (zccache patch removal) stays blocked until 4.5.2 publishes.

    The other Tracks (3 + 4) are unaffected — slices 15, 16, 17, 33 all merged this session; only slice 32 (Option B broker-owned bind) and slice 34 (launcher-budget integration test) remain in Track 4, both substantial.

  3. 14 remaining items

  4. zackees commented on Aug 1, 2026

    @zackees
    OwnerAuthor

    Slice 32 is functionally complete. #824 merged as 99a2e6a (27/27) — the launcher half, opt-in and off by default.

    Piece PR
    broker_owned_bind primitive (bind / prepare / recover) #812
    Adoption gated on SO_TYPE, SO_ACCEPTCONN where the platform supports it #812
    Endpoint outlives the broker's listener (disown_endpoint) #814
    Cross-process handover conformance #820
    Daemon adopts an inherited listener in bootstrap #823
    Launcher binds the endpoint itself #824

    RUNNING_PROCESS_BROKER_OWNED_BIND=1 turns it on. Unset — the default — every byte of the old allocate → spawn → probe path runs.

    The default-off is load-bearing, not caution

    Until it is settled who unlinks the socket at backend teardown, a broker-owned endpoint is never unlinked: enabling this by default would leak a socket file per launch. That is the concrete cost of the open question, and it is why the flag exists rather than a straight switch-over.

    The test pins the default rather than describing it — flipping opted_in to default-on fails the_launcher_opt_in_is_off_by_default_and_respects_an_explicit_zero. Someone changing that default without noticing the teardown question is exactly the regression the shape guards against.

    What is now decidable with evidence rather than in the abstract

    Both questions can be answered by trying it:

    1. Windows Option A vs option 2. Everything here is Unix-only and support() reports the Windows gap with a reason. The question is whether "the endpoint is reachable before the daemon runs" becomes a cross-platform guarantee or stays a documented Unix optimisation.
    2. Socket teardown ownership. The broker is the better candidate — it already owns backend lifecycle, and a daemon that dies unexpectedly cannot clean up after itself. With no socket file on Windows, this is a third per-platform divergence rather than a detail.

    Answering (2) is what would let the flag flip to on by default. Answering (1) decides whether it eventually disappears in favour of one path everywhere.

    Remaining on this meta issue

    Slice 34 (launch_fails_if_endpoint_not_probe_able_within_budget) and the rest of the v2 baseline. Slice 32 itself needs no more code — only the teardown decision before it can become the default.

  5. zackees commented on Aug 1, 2026

    @zackees
    OwnerAuthor

    Slice 34 landed — #825 merged as d657f78 (15/15).

    launch_fails_if_endpoint_not_probe_able_within_budget pins the property everything else in the broker assumes: a backend whose daemon starts but never reaches bind fails the launch rather than hanging the caller. testbin-sleeper is the fixture because it is a well-behaved process that is useless as a daemon — a live child that never serves is the case where "wait a bit longer" always looks reasonable.

    Worth recording that the first revision of that test passed while proving nothing: it asserted only is_err(), and the launcher rejected the fixture in 351µs with BinaryOutsideAllowRoot because per_version_binary_dir was "." while the binary lives under target/. Nothing was spawned. The assertion now requires BackendLaunchError::BackendHandle specifically, so a pre-spawn validation error can no longer masquerade as a probe timeout.

    Slices 32 and 34 are both complete

    Piece PR
    broker_owned_bind primitive #812
    Adoption gated on SO_TYPE / SO_ACCEPTCONN #812
    Endpoint outlives the broker's listener #814
    Cross-process handover conformance #820
    Daemon adopts an inherited listener #823
    Launcher binds the endpoint (opt-in) #824
    Launch gives up on a non-binding daemon #825

    What is left on this meta issue, and what it needs

    Nothing in slices 32/34 needs more code. Two decisions gate what happens next, and both have been open across this whole run:

    1. Who unlinks the socket at backend teardown? This is what keeps RUNNING_PROCESS_BROKER_OWNED_BIND opt-in — until it is answered, a broker-owned endpoint is never unlinked and default-on would leak a socket file per launch. The broker looks like the right owner: it already owns backend lifecycle, and a daemon that dies unexpectedly cannot clean up after itself.
    2. Does Windows eventually get option 2 (broker pre-creates the first pipe instance), or stay on Option A permanently? This decides whether "reachable before the daemon runs" is a cross-platform guarantee or a documented Unix-only optimisation.

    Both are now answerable by experiment rather than argument — the flag turns the path on and the conformance tests observe it.

    The rest of the meta issue (v2 baseline release, zccache patch removal, #782 Phase A) is consumer-side and outside what this repo can close.

  6. zackees commented on Aug 1, 2026

    @zackees
    OwnerAuthor

    Orphaned-socket defect fixed — #826 merged as 41c13a0 (27/27).

    I introduced this in #824 and found it re-reading my own merged code. disown_endpoint fired immediately after spawn, so a failed probe killed the child and dropped the broker's listener with its reclaim guard already released: dead daemon, socket file left behind, once per failed launch.

    The conceptual error is worth naming because it is easy to repeat: a child existing is not the same as a child owning the endpoint. The probe is what decides that, which is exactly what #814 said the reclaim guard was for. Disowning now happens only on the success path.

    Why this matters for the teardown question

    It sharpens question (1) below rather than answering it. There are two distinct lifetimes here, and only one is still open:

    • Failed launch — nothing ever owned the endpoint, so the broker cleans up. Settled, and now tested.
    • Successful launch, daemon exits later — a daemon did own it. Who unlinks it then is the open question, and it is what keeps RUNNING_PROCESS_BROKER_OWNED_BIND opt-in.

    So the leak surface is now strictly the second case. That is a smaller and better-defined decision than "who owns teardown" in general.

    Slice 32 / 34 state

    All merged: #812 (primitive), #814 (endpoint outlives the listener), #820 (cross-process conformance), #823 (daemon adopts), #824 (launcher, opt-in), #825 (launch gives up on a non-binding daemon), #826 (no orphan on failure).

    Still gated on decisions, not code:

    1. Who unlinks the socket when a successfully-launched daemon exits? Answering this is what lets the opt-in become the default.
    2. Does Windows eventually get option 2, or stay on Option A permanently? This decides whether "reachable before the daemon runs" is a cross-platform guarantee or a documented Unix-only optimisation.
  7. zackees commented on Aug 1, 2026

    @zackees
    OwnerAuthor

    Teardown cleanup narrowed — #828 merged as cfd3f93 (27/27).

    BackendHandle::shutdown now removes the socket after it has watched the daemon exit. Worth noting this leaked in the pre-existing spawn-then-probe path too: a signalled daemon never runs its own reclaim, so the endpoint outlived it regardless of who bound it. Not a broker-owned-bind defect.

    force_kill deliberately does not clean up — it signals and returns without confirming exit, so removing the name there could unlink the socket of a daemon still serving. An unreachable-but-live backend is worse than a stale file.

    What this does and does not settle for the opt-in

    The teardown question splits into three cases. Two are now closed:

    Case Who cleans up State
    Launch fails broker (listener drops with its reclaim guard armed) closed — #826
    Broker tears the daemon down (shutdown) broker, after confirming exit closed — #828
    Daemon exits on its own (idle timeout, crash) undecided open

    The third is what still keeps RUNNING_PROCESS_BROKER_OWNED_BIND opt-in, and it is now the only thing. Under broker-owned bind the daemon's adopted listener has no reclaim guard (From<OwnedFd> gives ReclaimGuard::default()), so a self-exiting daemon leaves its endpoint behind.

    Options, roughly:

    1. The daemon reclaims on clean exit — it knows it is exiting, but a crash or SIGKILL still skips it, so this covers the polite case only.
    2. The broker sweeps on next launch for the same service — catches every case eventually, at the cost of the broker knowing about endpoint naming.
    3. Accept it and document it — the allocator generates a fresh random path per launch, so a stale file is never reused; it accumulates rather than breaking anything.

    (3) is more defensible than it first sounds given (1) cannot cover crashes, but it is a decision about what the runtime directory is allowed to look like, not a code gap. That is the call I have not made.

    Everything else in slices 32/34 is merged and tested.

  8. zackees commented on Aug 1, 2026

    @zackees
    OwnerAuthor

    Option 1 from my previous comment is not implementable. Ruling it out on evidence rather than taste, so the remaining decision is between two options instead of three.

    I proposed "the daemon reclaims its socket on clean exit". Two things in the current design prevent it:

    BrokeredBackend::serve returns Never. A brokered daemon does not return through bootstrap — it serves until something terminates it. There is no post-serve point at which generic cleanup could run, and adding one would mean changing serve's signature, which exists specifically to prove divergence at the type level.

    An adopted listener's reclaim guard cannot be armed. interprocess exposes ListenerOptions::reclaim_name(bool) at bind time and do_not_reclaim_name_on_drop() to disarm, but nothing to arm a guard after the fact. The daemon's listener comes from From<OwnedFd>, which is ReclaimGuard::default() — no guard, no way to add one.

    An individual BrokeredBackend implementation could still unlink before calling process::exit on its own idle path, but that is per-daemon discipline, not something the framework can guarantee — and it still misses crashes and SIGKILL.

    So the self-exit case comes down to

    Option 2 — the broker sweeps. On the next launch for a service, remove endpoints belonging to daemons that are gone. Catches every case eventually, including crashes. Costs the broker some knowledge of endpoint naming and a liveness check per stale candidate.

    Option 3 — accept it, and say so. The allocator generates a fresh random path per launch, so a stale socket is never reused; it accumulates. On Linux $XDG_RUNTIME_DIR is tmpfs cleared at logout; on macOS $TMPDIR is per-user and periodically swept. The cost is untidiness in a directory that is already ephemeral, not a correctness problem.

    I lean to (3) for now and (2) only if the accumulation is ever observed to matter — a sweep introduces its own failure mode, deciding a socket is dead while a daemon still holds it, which is the same hazard that made force_kill skip cleanup in #828.

    But this is a call about what the runtime directory is allowed to look like, and it is the last thing gating
    RUNNING_PROCESS_BROKER_OWNED_BIND from becoming the default. Everything else in slices 32/34 is merged and tested.

  9. zackees commented on Aug 1, 2026

    @zackees
    OwnerAuthor

    Slice 32 is done. #835 merged as 1e5121a (27/27) — broker-owned bind is on by default, and the last open question is answered.

    The teardown question, settled

    Case Resolution
    Launch fails broker cleans up — listener drops with its reclaim guard armed (#826)
    Broker tears the daemon down broker cleans up after confirming exit (#828)
    Daemon exits on its own accepted, not swept (#835)

    Accepted because sweeping costs more than the untidiness. The allocator issues a fresh random path per launch, so a stale entry is never reused — it accumulates in a directory that is already ephemeral. A sweep would have to decide a socket is dead while a daemon might still hold it, which is the hazard that made force_kill skip cleanup in #828.

    The third option I originally floated — "the daemon reclaims on clean exit" — is not implementable: BrokeredBackend::serve returns Never, so there is no post-serve point, and an adopted listener's reclaim guard cannot be armed after the fact. Ruling that out on evidence is what reduced the choice to two.

    RUNNING_PROCESS_BROKER_OWNED_BIND=0 remains the escape hatch, and a test pins it specifically — if it stops working the fallback to spawn-then-probe becomes unreachable, which is what an operator would need in a hurry.

    Slice 32 / 34, complete

    Piece PR
    broker_owned_bind primitive #812
    Adoption gated on SO_TYPE / SO_ACCEPTCONN #812
    Endpoint outlives the broker's listener #814
    Cross-process handover conformance #820
    Daemon adopts an inherited listener #823
    Launcher binds the endpoint #824
    Launch gives up on a non-binding daemon (slice 34) #825
    No orphaned socket on a failed launch #826
    Broker removes the socket it tore down #828
    On by default #835

    Still open on this meta issue — and not closable from here

    Only the Windows contract question remains as a design item: whether Windows eventually gets a pre-created first pipe instance, or stays on spawn-then-probe permanently. Nothing is blocked on it — support() reports the gap with a reason and the fallback applies — but it decides whether "reachable before the daemon runs" is a cross-platform guarantee or a documented Unix optimisation.

    The rest of #500 (v2 baseline release, zccache patch removal, #782 Phase A) is consumer-side and cannot be closed from this repository.

  10. zackees commented on Sep 30, 2026

    @zackees
    OwnerAuthor

    Planning pass (automated, Opus):

    Row-by-row verification (running-process main @ 2ee2081, plus zccache main)

    Row State Evidence
    Slice 30: v2 release Done Shipped in #501; releases continued through 4.10.15 (#1223)
    Slice 31: drop zccache [patch.crates-io] pin Done (moot) zccache's root Cargo.toml has no running-process dependency and no patch section. Its comment reads "no local path or patch override remains". It now gets systems code through kernal-api =0.1.23.
    Slices 15/16/17 (#782 Phase A) Done zccache#806/#807/#808; forge-ci/zccache#782 is CLOSED
    Slice 32: broker-owned bind Done (Unix) #812-#835; RUNNING_PROCESS_BROKER_OWNED_BIND is on by default
    Slice 33: trybuild harness Done #502
    Slice 34: probe budget test Done #825

    All four Done-When criteria are met. None of them covered Windows.

    What is still open

    One design question from the last comment: should Windows get a pre-created first pipe instance, or stay on spawn-then-probe permanently? Nothing depends on the answer. broker_owned_bind::support() already reports the Windows gap, and the spawn-then-probe fallback applies there.

    Decision

    Close this meta. It is the smallest safe option: every row and every Done-When criterion is met. Keeping a tracker that is marked "not a fix target" open for one unscheduled design question just invites more re-audits like the ones already recorded here.

    Record the Windows question as its own design issue. Recommended default: spawn-then-probe is the permanent Windows contract, and broker-owned bind is a documented Unix-only optimisation. The reason is structural (see the earlier comment): a Windows named-pipe listener is a single pipe instance, not a bound object. A pre-created instance would serve only the first client, and it would need a new handle-inheritance path through spawn_daemon, all for little gain.

    Plan if the default is taken

    No code PR. One docs-only PR that states, in the Broker section of docs/ and CLAUDE.md, that reachability before the daemon runs is Unix-only by design.

    • Verify: ./lint
    • CI impact: none
    • Risk: none

    Question for the maintainer

    Close #500 now, and treat spawn-then-probe as the permanent Windows contract? Recommended default: yes to both. Open a dedicated issue only if Windows launch latency is ever measured to be a problem.

  11. zackees commented on Sep 30, 2026

    @zackees
    OwnerAuthor

    Closing per the planning comment above: every row and every 'done when' criterion is met (zccache no longer pins running-process, zccache#782 is closed, the v2 baseline shipped). Windows stays on spawn-then-probe permanently; the broker-owned bind is documented as a Unix-only optimisation. New work has its own issues (#1258, #974, #975, #1101, #850).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions