Repository navigation
Tags
Tags
fix: a cold conversation takes the reset order instead of its old acc… …ount (#620) * fix: a cold conversation takes the reset order instead of its old account Scope: selection (session pin), proxy (bound-history hold), routing doc. Reported 2026-11-07 on a two-Ultra fleet: the account resetting in 3d 14h at 95% carried the day's traffic while the one resetting in 11h sat at 16%. The weekly-reset order only placed NEW conversations. A conversation that had run on an account stayed there two ways, however long it idled: - its session pin was honoured at any idle time while the account was under the switch threshold (only an over-threshold pin re-keyed when idle-cold); - a history carrying thinking signatures minted there was held to that account for BOUND_TOKEN_TTL_MS, 24 hours, every other account benched. Both exist for the prompt cache, and a session idle past its warm window (5 minutes, 55 for a 1h-cache client) has none left anywhere. Now: - select_with_group gives up a cold pin and takes the normal pick, which re-pins wherever it lands; the re-pin line says "cache cold: idle Ns" instead of naming a hard gate. - the proxy does not hold a cold conversation whose only bound state is thinking, for a model whose thinking reads on any account (Opus 5.5). Advisor results and thread state (new BoundHistory field carries_account_only_state), Sonnet/Haiku 5.5 thinking, and requests with no model keep today's hold. Coldness is Manager::bound_history_is_cold: the newer of the session pin's stamp and the newest mint, so either sign of life keeps the hold. Tests: a_cold_pin_takes_the_reset_order_again and a_cold_opus_conversation_is_not_held_by_its_thinking fail with either change switched off and pass with it; a_warm_pin_keeps_its_account_over_ the_reset_order is the complement. Two existing tests encoded the old rule and are updated: the days-stale pin test keeps its no-eviction half, and hold_expiry_leaves_the_session_on_its_failover now keeps its session active through the hold, which is the case it is about. docs/architecture.md: the selection sort now names the reset-urgency bucket, which it had omitted, and a section states the warm-only pin. Claude-Session: https://claude.ai/code/session_01MhoEKiMVoeJQUzk4TKfTS7 * fix: write the re-pin log choice as if let, which clippy requires Claude-Session: https://claude.ai/code/session_01MhoEKiMVoeJQUzk4TKfTS7
fix: hide the 5h window on a Claude plan that has none (#616) Scope: tcr status --json and TcrBar. An Anthropic row whose usage read said "five_hour": null (Ultra) now reports quotaWindows: ["sevenDay"] on tcr status --json; every other Anthropic row still carries no quotaWindows key, so the status-contract fixture is unchanged. TcrBar drops the provider == .codex gate on reportedWindows and on the card's quota rows, so a Claude row honours quotaWindows: an Ultra card draws one 7d row, its line leaves the 5h slot blank with its width kept, and its fleet-strip segment fills from the week instead of grey. The strip's Claude and Codex rules are now one function (liveRoom); a Claude row with a 5h window and no 5h reading still stays grey. Claude-Session: https://claude.ai/code/session_01MhoEKhygdXtUnsRotKmDT2
fix: honor an explicit five_hour null and the provider plan name (#614) * fix: honor an explicit five_hour null and the provider's plan name Scope: probe, quota, warm, status text, plan label. The usage endpoint now answers "five_hour": null for an Ultra org (measured 2026-11-01). probe::WindowReading types that apart from a missing key: PlanHasNone clears Quota::five_hour and sets five_hour_absent_on_plan, which the warmer skips and tcr status text renders as 5h=none (carried on the server-to-CLI status wire, skipped when false). A 5h header or bucket clears it. The profile's organization.plan_display_name ("Ultra") travels beside the organization_type / rate_limit_tier / seat_tier triple everywhere, is overwritten on a fresh read and never blanked by None, and wins tcr_status_wire::plan_label when non-empty. A row profiled before the field existed is asked once more per process. Claude-Session: https://claude.ai/code/session_01MhoEKhygdXtUnsRotKmDT2 * fix: fill the demo row's plan_display_name_asked field The `tcr demo` AccountRuntime initializer in src/demo.rs lacked the field #614's first commit added, and every CI job stopped on E0063 at demo.rs:48. Claude-Session: https://claude.ai/code/session_01MhoEKhygdXtUnsRotKmDT2 * fix: leave planDisplayName out of a status row that has none Three status-contract tests compared a `tcr status --json` row against the committed fixture and found a new `planDisplayName: null` key on every row; the key is now written only when the provider reported a name, so rows without one serialize as before. The "already answered" manager test now gives its config row a display name too, since a row with a plan but no display name is asked once per process by design. Claude-Session: https://claude.ai/code/session_01MhoEKhygdXtUnsRotKmDT2
feat: forecast quota demand and calibration in the routing document (#… …608) * test: the routing document is served locally Adds proxy::forecast_routing_tests::schema_one. GET /_tcr/routing, driven through the router with a fake proxy key and a loopback ClientAddr, must answer 200 and name schema 1. The status is asserted before the body is decoded. At this base no such route exists, so the /_tcr catch-all answers a local 404 and the assertion fails on 404 against 200. * refactor: one candidate filter for every pool pass Extract Function on the selector's pool pick in src/manager/select.rs. Each pool pass was a candidate filter written inline in its own loop: pick_eligible, pick_least_loaded, pick_fable_last_resort, and the hard gate closure of select_revalidation's fallback. Those filters now live in one Manager::pool_candidate(pass, ...), keyed by a PoolPass, and each pick keeps only its ranking key. The gates stay with their owners: eligible, hard_ok, model_blocked and pool_pick_respects_control_reserve. The request facts the filters read are gathered once in a PoolAsk, and the control account exclusion an inference pick adds to tried moves to Manager::control_excluded. The picks now read the one version of the group settings their selection already took, rather than taking it again. No pick changes order or result: the existing selector suite guards it. * test: the routing document, its selector seam and its CLI exits The tests the routing document's green names, ahead of the production change they guard. They name the API that change adds, so this commit does not build on its own; the red that runs at the base is schema_one. - src/manager/select.rs routing_candidates_tests: routing reads what the picks serve, pass by pass (lock, paced, least loaded, Fable last resort, revalidation, none), and a routing read changes no selector state. - src/proxy.rs forecast_routing_tests: every schema field and its type, no account, organization, session or credential in the output, an unroutable model's reason is that no available account can serve it now, GET only, the local gate, and transport, 503 and model refusal errors. - src/routing.rs: the ten-minute error window, the error kinds a status and a stream name, the model catalogue, the state rule and the reader. - src/manager/usage.rs: served models, failed streams and refused credentials are observed where they are already recorded. - src/model.rs: the four model families. - src/cli.rs, src/main.rs, tests/status_check.rs: tcr routing --json exits 0 served, 3 with nothing listening, and 4 for a refused key, an HTTP error, no answer in time, an unreadable document or another schema. * feat: serve the routing document and print it with tcr routing --json GET /_tcr/routing answers schema 1 of the routing document behind the local endpoint gate, no-store, and every other method a local 405. Which model families each provider pool can serve a new unpinned request for now comes from Manager::routing_candidates, the same pool_candidate filter the picks run, in the selector's own pass order (account lock, paced, least-loaded, Fable last resort, revalidation), read without stamping, pinning or spending a revalidation window. forecast is null. The manager keeps, per provider, the model ids served this boot and the last upstream error kind with a ten-minute count in fixed per-second slots, recorded where the proxy already sees a send fail, a status, an unreadable body, a model refusal, a refused credential or a failed stream. Nothing in selection reads it. tcr routing --json prints the served document and exits 0; 3 when no proxy listens; 4 on a rejected key, an HTTP error, a timeout, or an answer that is not a readable schema-1 document. Nothing is built offline. --json is required. tcr status --json, its renderer and the status wire contract are unchanged. Also two needless crate:: paths and a missing semicolon in the tests of the previous commit, which clippy refuses; no assertion changes. Files: src/cli.rs, src/lib.rs, src/main.rs, src/manager/mod.rs, src/manager/select.rs, src/manager/usage.rs, src/model.rs, src/proxy.rs, src/routing.rs. * test: routing agrees with the selector, frees, redirects, catalogue and HEAD * test: use the probe modules existing imports * fix: make routing readiness and clear times match selection * fix: make the revalidation valve accessor const * test: the catalogue names the model the response served * fix: catalogue the model named by each served response * test: an unmetered terminal keeps the metered usage model * fix: retain the model attached to metered usage * test: separate the digits of the priced replay literal * test: spacing expires on the replay clock * test: drive quota workloads through the real selector * test: prove replay accounting and clock invariants * test: freeze quota replay scores and preserve FIFO retries * feat: supply observation time to quota replay operations * test: run the ten-case quota replay by name * test: require estimates from ledger history and quota ticks * test: cover forecast calibration and checked history replay * feat: estimate demand and calibrate quota from completed usage * test: prove restored estimates add no upstream requests * test: wait for listener positive controls without a polling race * test: require demand history to follow explicit record time * fix: start demand history on its observed record clock * test: require header tick calibration and preserve probe intervals * feat: calibrate from first-hand quota header ticks * test: reject calibration past a reading watermark * fix: align calibration vectors with reading watermarks * test: require replay quota refusals to arm production holds * fix: arm production quota holds during replay * test: exclude pacing-only waits from replay lockout * test: distinguish revalidation pacing from hold lockout * fix: wake quota replay at revalidation deadlines * test: guard frozen replay scores in the default suite * test: assert send stamps directly against the wall clock * docs: name the production quota refusal fold * docs: describe the scenario pacing settings * docs: mark explicit send entry as replay only * refactor: share the upstream error observation clock * refactor: declare quota replay profiles once * test: distinguish model gates and early resets from pacing * fix: classify replay waits with the real hard serve gates * test: freeze corrected quota replay measurements * test: inline the score in the model gate lockout message * test: bound calibration records while an account stays busy * fix: bound pending calibration records and invalidate dropped intervals * test: reject calibration records at the lower reading watermark * test: guard calibration invalidation after pending overflow * fix: prune forecast history only when the newest hour or minute moves History::record walked every hours, covered and minutes map on every completed request. Its cutoffs move only when the newest record enters a new hour or minute, so it now remembers the last pruned bucket and skips the walk otherwise. A minute already behind the retained hour is not inserted and cover_day does not insert hours behind the 28-day cutoff, so nothing is left for a skipped prune to remove: after every record the maps equal what pruning on every record kept, which a new test checks against that reference over 4,000 out-of-order records. Measured with a standalone copy of record/cover_day (2.15M replayed lines, then 200,000 steady records): steady per record 2.7-6.2 us before (22-32 us with the machine loaded), 115 ns after; the variant with no pruning at all ran 122 ns. Claude-Session: https://claude.ai/code/session_01MhoEKhRKxpEe1kHaV6XYxF * fix: read the forecast history once at boot, never on a handoff rebuild replay_window read 29 days of ledger files for the forecast history on every call, so a handoff rebuild re-read all of them while holding the rebuild gate that every UsageTracker::record waits on. The history pass now runs only when attach_ledger passes a History in, reading each of the 29 files once alongside the quota replay; rebuild_from_ledger replays the quota window only (days_back: 2 files, 8 with Codex) and leaves the history that record has fed since boot. Cost: lines the predecessor wrote after this process's boot replay are in the rebuilt totals but not in the history. history.partial is now sticky until the next boot, said where it is set. Measured on a copy of the last 29 days of a real ledger (49 MB compressed), release build, loaded machine: attach_ledger 2.15 s, rebuild_from_ledger 42 ms. The boot pass still runs before the listener binds; it is over a second and no background task is added for it. New test: deleting the day-five file after boot leaves the estimates of a rebuild unchanged. Claude-Session: https://claude.ai/code/session_01MhoEKhRKxpEe1kHaV6XYxF * fix: train the forecast on a served response's own rate-limit headers The proxy holds the request's in-flight guard when it folds the response's headers (proxy.rs, enter_in_flight before the send, update_quota once the headers arrive), so account.in_flight was never 0 there and every response-header reading was "unfinished": it only cleared the fit's scales, and calibrated was false whenever the proxy was busy. The tests passed because they read headers with no guard held, a state production never has. Ordering read from the code: headers -> update_quota; then the body; then ServedTurn::finish -> record_usage, stamped with now_ms() at the body's end (proxy/served.rs; non-streamed in the handler, streamed in the parse task). So the request's own usage always lands after its own reading and belongs to the next interval. update_quota_serving takes the guard (its idx is the account) and treats the reading as unfinished only when another request on the account is open (in_flight > 1). update_quota and the probe path keep in_flight != 0. That record can carry the reading's own millisecond, which the lower-watermark rule invalidates. Calibration::observe_serving marks a reading as serving, and only then is a record at exactly its millisecond kept for the next interval; probe readings keep the rule the reviewer test pins. Unfinished readings no longer build header_ticks under the accounts write lock; carries_ticks answers the only question they need. The header names are constants, so header_ticks makes no format! calls. Tests: the response-header unit test now takes each reading with the request's guard held and records in the same millisecond, and checks a reading with a second request open stays uncalibrated. A new proxy test sends 40 non-streamed requests through the router to a stub whose headers rise one percent a request and checks the routing document is calibrated. Claude-Session: https://claude.ai/code/session_01MhoEKhRKxpEe1kHaV6XYxF * refactor: name the Fable family once and record what MAX_SAMPLES costs The two "fable" literals in Calibration are one FABLE constant. MAX_SAMPLES gets the measurement the review asked for, and it does not justify 2048: fit over a full deque of four families cost 0.06 ms at 256 samples, 0.2 ms at 1024 and 3.7-6.7 ms at 2048 (standalone copy of fit, rustc -O), on every rising tick of each of three windows under the accounts write lock. Left at 2048 here because the same constant caps pending records, which needs its own number; the comment says so. The history.partial stickiness comment landed with the rebuild change. Claude-Session: https://claude.ai/code/session_01MhoEKhRKxpEe1kHaV6XYxF * test: use is_multiple_of in the served-headers calibration test clippy --all-targets -D warnings rejects n % 2 == 0 (manual_is_multiple_of). Claude-Session: https://claude.ai/code/session_01MhoEKhRKxpEe1kHaV6XYxF --------- Co-authored-by: Eran Avrahami <eranavrahami@MacBook-Pro-3.local> Co-authored-by: Gil Portnoy <dddhkts1@gmail.com>
test: replay quota workloads (#600) * test: the routing document is served locally Adds proxy::forecast_routing_tests::schema_one. GET /_tcr/routing, driven through the router with a fake proxy key and a loopback ClientAddr, must answer 200 and name schema 1. The status is asserted before the body is decoded. At this base no such route exists, so the /_tcr catch-all answers a local 404 and the assertion fails on 404 against 200. * refactor: one candidate filter for every pool pass Extract Function on the selector's pool pick in src/manager/select.rs. Each pool pass was a candidate filter written inline in its own loop: pick_eligible, pick_least_loaded, pick_fable_last_resort, and the hard gate closure of select_revalidation's fallback. Those filters now live in one Manager::pool_candidate(pass, ...), keyed by a PoolPass, and each pick keeps only its ranking key. The gates stay with their owners: eligible, hard_ok, model_blocked and pool_pick_respects_control_reserve. The request facts the filters read are gathered once in a PoolAsk, and the control account exclusion an inference pick adds to tried moves to Manager::control_excluded. The picks now read the one version of the group settings their selection already took, rather than taking it again. No pick changes order or result: the existing selector suite guards it. * test: the routing document, its selector seam and its CLI exits The tests the routing document's green names, ahead of the production change they guard. They name the API that change adds, so this commit does not build on its own; the red that runs at the base is schema_one. - src/manager/select.rs routing_candidates_tests: routing reads what the picks serve, pass by pass (lock, paced, least loaded, Fable last resort, revalidation, none), and a routing read changes no selector state. - src/proxy.rs forecast_routing_tests: every schema field and its type, no account, organization, session or credential in the output, an unroutable model's reason is that no available account can serve it now, GET only, the local gate, and transport, 503 and model refusal errors. - src/routing.rs: the ten-minute error window, the error kinds a status and a stream name, the model catalogue, the state rule and the reader. - src/manager/usage.rs: served models, failed streams and refused credentials are observed where they are already recorded. - src/model.rs: the four model families. - src/cli.rs, src/main.rs, tests/status_check.rs: tcr routing --json exits 0 served, 3 with nothing listening, and 4 for a refused key, an HTTP error, no answer in time, an unreadable document or another schema. * feat: serve the routing document and print it with tcr routing --json GET /_tcr/routing answers schema 1 of the routing document behind the local endpoint gate, no-store, and every other method a local 405. Which model families each provider pool can serve a new unpinned request for now comes from Manager::routing_candidates, the same pool_candidate filter the picks run, in the selector's own pass order (account lock, paced, least-loaded, Fable last resort, revalidation), read without stamping, pinning or spending a revalidation window. forecast is null. The manager keeps, per provider, the model ids served this boot and the last upstream error kind with a ten-minute count in fixed per-second slots, recorded where the proxy already sees a send fail, a status, an unreadable body, a model refusal, a refused credential or a failed stream. Nothing in selection reads it. tcr routing --json prints the served document and exits 0; 3 when no proxy listens; 4 on a rejected key, an HTTP error, a timeout, or an answer that is not a readable schema-1 document. Nothing is built offline. --json is required. tcr status --json, its renderer and the status wire contract are unchanged. Also two needless crate:: paths and a missing semicolon in the tests of the previous commit, which clippy refuses; no assertion changes. Files: src/cli.rs, src/lib.rs, src/main.rs, src/manager/mod.rs, src/manager/select.rs, src/manager/usage.rs, src/model.rs, src/proxy.rs, src/routing.rs. * test: routing agrees with the selector, frees, redirects, catalogue and HEAD * test: use the probe modules existing imports * fix: make routing readiness and clear times match selection * fix: make the revalidation valve accessor const * test: the catalogue names the model the response served * fix: catalogue the model named by each served response * test: an unmetered terminal keeps the metered usage model * fix: retain the model attached to metered usage * test: separate the digits of the priced replay literal * test: spacing expires on the replay clock * test: drive quota workloads through the real selector * test: prove replay accounting and clock invariants * test: freeze quota replay scores and preserve FIFO retries * feat: supply observation time to quota replay operations * test: run the ten-case quota replay by name * test: require replay quota refusals to arm production holds * fix: arm production quota holds during replay * test: exclude pacing-only waits from replay lockout * test: distinguish revalidation pacing from hold lockout * fix: wake quota replay at revalidation deadlines * test: guard frozen replay scores in the default suite * test: assert send stamps directly against the wall clock * docs: name the production quota refusal fold * docs: describe the scenario pacing settings * docs: mark explicit send entry as replay only * refactor: share the upstream error observation clock * refactor: declare quota replay profiles once * test: distinguish model gates and early resets from pacing * fix: classify replay waits with the real hard serve gates * test: freeze corrected quota replay measurements * test: inline the score in the model gate lockout message --------- Co-authored-by: Eran Avrahami <eranavrahami@MacBook-Pro-3.local> Co-authored-by: Gil Portnoy <dddhkts1@gmail.com>
fix: TcrBar repoints an older tcr on PATH at its bundled copy (#604) Scope: apps/macos. Sparkle updates TcrBar.app and never the tcr a shell finds on PATH, and the shell's claude runs bare `tcr run`. A 0.2.28 CLI beside a 1.2.18 app handed Claude Code the legacy CA file while the server signed with tcr-ca.pem, so every request failed with UNABLE_TO_VERIFY_LEAF_SIGNATURE. At every launch, off the main thread, PathCliRepair replaces the PATH tcr with a symlink to the bundled one (symlink at a temp name in the same folder, then rename(2)) only when it is a regular file, the bundled tcr runs --version, the PATH copy is strictly older by major.minor.patch, the app is installed under an Applications folder, and the folder is writable. Every other case is left alone. Each outcome is a typed enum case logged as one NSLog line. Claude-Session: https://claude.ai/code/session_01MhoEKgVSjiCbmmK3EtCLmn
fix: exec the agent from tcr run, so the terminal sees it as its prog… …ram (#602) * fix: exec the agent from tcr run, so the terminal sees it as its program * fix: name the tool helper directory by its setup, so exec leaves nothing to clean up
docs: rebind_served_history says it drops thinking, and two tests pin… … the drop (#598) Scope: manager/bound_tokens. Follow-up to #595. The doc summary still said the function returns how many entries moved, while it now returns moved plus dropped. The debug line said "dropped the thinking it did not read" on every move, including within one organization where nothing was dropped; it now names only the re-bind, and the counts stay in its fields. Two tests pin paths #595 left untested: - a redacted thinking block served across organizations by an org-bound model is dropped like a signature; - org-bound thinking moved to or from an account whose organization is unknown is dropped. Keeping it on the minting account would bring back the pull-back #595 removed, and binding it to the serving account would claim that account reads thinking it may have dropped. Watched both fail: with RedactedThinking taken out of the thinking match, the first test saw Some((2, RedactedThinking)); with an unknown organization treated as inside in outside_org_of, the second saw Some((1, ThinkingSignature)). Both pass on the restored code (manager::bound_tokens::tests, 23 passed). Claude-Session: https://claude.ai/code/session_01MhoEKfvNQZszpnkDfgWpey
fix: the shell prefix runs the shell Claude names, read when the comm… …and runs (#591) * fix: the shell prefix runs the shell Claude names, read when the command runs Scope: tool_env. Once CLAUDE_CODE_SHELL_PREFIX is set, Claude Code runs every command as `<prefix> '<script>'`, so the prefix tcr writes is what picks the shell. tcr baked that shell in at launch from CLAUDE_CODE_SHELL or SHELL, but Claude usually gets CLAUDE_CODE_SHELL from settings.json, which the launch never sees. A user with CLAUDE_CODE_SHELL=/opt/homebrew/bin/bash in settings.json and a zsh login shell got zsh on every tool call. The prefix now reads CLAUDE_CODE_SHELL from the command's own environment and uses it when it names a bash or zsh that resolves; the launch-time shell stays the fallback. A caller's own prefix is unchanged. Test: shell_prefix_runs_the_shell_claude_names_at_command_time covers set, unset and a bogus path. With the src change reverted it fails with "missing set=zsh: set=bash". Claude-Session: https://claude.ai/code/session_01Xg3GDUbVn1aXzPREZabiNr * fix: the new shell-choice test passes clippy Scope: tests/tool_proxy_env.rs. Drops a needless std::path qualification and backticks two names in the doc comment. CI's `cargo clippy --workspace --all-targets --locked -- -D warnings` now exits 0 locally. Claude-Session: https://claude.ai/code/session_01Xg3GDUbVn1aXzPREZabiNr
fix: count usage once a replaced proxy has finished writing (#588) * test: usage after a listener handoff is counted once Three tests, red at this commit: - a rebuild from the day file counts every line of both writers, and every request recorded during the rebuild, exactly once; - the successor counts its predecessor's last lines once the handoff channel closes; - the predecessor keeps the handoff channel open until its usage ledger is on disk. The plumbing they need changes no behaviour yet: ServeOptions carries a restart::Handover (the listener plus the handoff channel) instead of a bare listener, serve drops the channel as before, and UsageTracker::rebuild_from_ledger is a stub that rebuilds nothing. Claude-Session: https://claude.ai/code/session_01AckcDeLNFYeL7YkUBd7hLj * fix: recount usage once a handoff's predecessor has finished writing After a listener handoff the successor replayed the day file at boot while the predecessor was still draining, so every line the predecessor wrote after that replay reached the file and never the successor's totals. tcr status and TcrBar undercounted until the next restart. A ledger line names no writer, so the two processes' lines are not told apart. Once the predecessor is done the file holds every line of both exactly once, and the successor recounts the day from it: - The predecessor keeps the handoff channel open from the moment it sends the listener until its shutdown has drained the usage ledger. The close is the signal; nothing new goes over the wire, and a crash closes it too. - The successor keeps its end (restart::Handover), waits for the close, bounded by the drain ceiling plus two shutdown graces, then calls UsageTracker::rebuild_from_ledger off the async workers. - The rebuild flushes this process's own queue and replays the same files boot replays into a fresh tracker, then swaps it in. A read-write gate makes each record's ledger line and bucket update one step to the rebuild, so a request recorded meanwhile waits and lands once. It refuses, and logs why, when the files cannot be trusted to hold this process's lines (writer failing, lines dropped, flush slow). Claude-Session: https://claude.ai/code/session_01AckcDeLNFYeL7YkUBd7hLj * test: a child test run that matched no test fails its parent Three tests re-run their own test binary as a child with --exact <name>. A name that matches no test (after a rename) runs nothing: the child prints "running 0 tests" and exits 0. - restart: the parent checked only the exit status, so it passed while checking nothing. It now requires "running 1 test" from the child, through test_support::libtest::assert_ran_one_test, with the child's stdout and stderr in the failure. - file_log: already failed (the child must exit 7), with no output to say why. It now captures the child's output, checks the same line and prints both streams on either failure. - server_replace: already failed (the child exits before readiness), with only the first stdout line in the message. Proxy::ready now collects the rest, so the message shows "running 0 tests". Claude-Session: https://claude.ai/code/session_01AckcDeLNFYeL7YkUBd7hLj * refactor: one constant for the first version with listener handoff "tcr 1.2.17 or newer" was a literal in five Rust files, messages and test assertions alike. restart::handoff_since! now holds the version, for the messages that are constants (concat!), and restart::HANDOFF_SINCE is the same value for format! and the tests. The copies Rust cannot reach (two TcrBar messages, one Swift test, docs/troubleshooting.md) are named beside the macro. A build_info test already fails when the troubleshooting doc stops naming the version. Claude-Session: https://claude.ai/code/session_01AckcDeLNFYeL7YkUBd7hLj * test: name the fan handoff tests for what they guard Three tests were named after the review that asked for them: - a_handoff_stops_the_predecessor_fan_before_releasing_the_listener - a_handoff_completes_when_the_fan_controller_has_already_stopped - a_stopped_predecessor_writes_no_fan_after_the_successor_hands_back_to_auto (crates/tcr-fan) No other test, file or CI filter named them. Claude-Session: https://claude.ai/code/session_01AckcDeLNFYeL7YkUBd7hLj * docs: a replaced server's usage is recounted once it exits docs/cli.md describes what --replace carries across. Usage is now one of those things: the successor recounts today from the ledger once the predecessor's last line is on disk. Claude-Session: https://claude.ai/code/session_01AckcDeLNFYeL7YkUBd7hLj
PreviousNext