Skip to content

ci: bound the jobs, and stop rebuilding the sidecars every run - #213

Merged
pathscale merged 23 commits into
masterfrom
ci/bound-and-cache-rust-job
Sep 23, 2026
Merged

pathscale merged 23 commits into
masterfrom
ci/bound-and-cache-rust-job

Conversation

@pathscale

Copy link
Copy Markdown
Owner

The Rust job on master ran past twenty minutes. Not wedged on anything we compile: clippy had been running since 02:33 where the same step takes 65s warm.

What was wrong

No cache for the Rust job. Every cache entry in the repo is scoped to refs/pull/211/merge and keyed Darwin-arm64 (the macOS panel job). Caches are branch-scoped and a branch can only read its own and its base's, so master never had one. With #211 merged, those entries are unreachable anyway. The Linux Rust job compiles the full dependency graph from scratch on every run.

No timeout-minutes on either job, so the ceiling was GitHub's six-hour default. That is what turns a slow step into an invisible one: the run does not fail, it just never finishes and the queue backs up behind it.

The sidecars rebuilt every run, ~2m30s combined. Both build outside the workspace target directory, which is what rust-cache keys on, so neither was ever cached.

What this does

  • timeout-minutes: 20 on Rust (5m warm, ~15m cold), 6 on Frontend (1m20s warm).
  • Caches the two sidecar binaries, keyed on the manifests, scripts, reader sources and GUI schema that decide them. runner.os is in the key so a Linux runner cannot pick up host-target-suffixed macOS binaries.

The sidecars are the safest things to cache: the proxy is a fixed-version crates.io install, and the reader is pinned to the v2-era WorkTable it must never move off.

Expected effect

First run on master populates rust-cache for the branch; subsequent runs read it. Steady state should be well under the 5m17s that the last warm branch run took.

meh added 23 commits September 18, 2026 09:36
The Rust job on master ran past twenty minutes with no end in sight. It was
not wedged on anything this repository compiles: clippy had been running
since 02:33 where the same step takes 65 seconds warm, because a cache
entry never existed for it. Every cache in the repository is scoped to
`refs/pull/211/merge` and keyed `Darwin-arm64`, which is the macOS panel
job. Caches are branch scoped and a branch can only read its own and its
base's, so nothing on master ever had one to read, and now that #211 is
merged those entries are unreachable anyway.

Neither job declared `timeout-minutes`, so the ceiling was GitHub's default
of six hours. That is the part that turns a slow step into an invisible
one: the run does not fail, it just never finishes, and the queue backs up
behind it. Both jobs now have a bound sized to their warm time with room
for a cold graph.

The sidecars are the other two and a half minutes. Both build outside the
workspace target directory, which is what `rust-cache` keys on, so neither
was ever cached, and both are the least likely things in the tree to
change: the proxy is a fixed-version crates.io install, and the reader is
pinned to the v2-era WorkTable it must never move off. They are now cached
as the produced binaries, keyed on the manifests, scripts, reader sources
and GUI schema that decide them, since the reader's build script derives
its schema copy from that. `runner.os` is in the key, so a Linux runner
cannot pick up the host-target-suffixed macOS binaries sitting in a
developer's checkout.
It was the longest step in the Rust job: 65 seconds warm, minutes cold.
`--all-targets` type-checks the test and bench targets on top of the lib and
bins, which is close to double the compile work, and `cargo test` on the
next line then does nearly all of it again.

A lint is worth most while the code is still open, which is the machine
doing the writing, not one reporting six minutes later. It stays wired up
here behind `workflow_dispatch` with a `clippy` input, so the machine's
answer is one manual run away when someone wants it. `inputs.clippy` is
undefined on push and pull_request, so the step is skipped there without
needing a second condition.

The exact local command is in the comment above the step, because it was
documented nowhere: not in a gates file, not in CLAUDE instructions, and the
only mention of clippy in `docs/` is one review from July.
A chat message read "and`WireMessage::Text`now takes`Utf8Bytes`": the spaces
around every inline code span were gone.

A paragraph is delimited by a blank line, so a single newline inside one is a
wrap in the source rather than a break the author asked for. Between two plain
words HTML collapses that newline to a space, which is why this went unnoticed
for so long. Next to an inline element it does not: a text node ending in
"and\n" followed by a <code> element has its trailing whitespace dropped at the
element boundary, and the two run together.

Normalising soft-wrap newlines to spaces before the inline split keeps the space
inside the text node, where the boundary cannot eat it.

In its own module rather than beside the renderer, so the test reaches it
without importing MessageBody, which pulls in the component library and a motion
entry point vitest cannot transform.
check_for_update went to the CDN with retry backoff even in a disposable QA
profile. The round trip took 1 to 2s (2156ms in the failing run) against the
2s window of settings-update-check-completes, so that check's verdict was
network latency. A QA process must also never be offered a published build to
install over the checkout it is testing.

With AZ_QA_WORKSPACE_ROOT set the command answers "no update" without the
network. The check now completes in 77ms and the outcome suite is 312 of 312.
button-sweep.sh waited for target/blitz-control.json, which nothing writes any
more, so every run ended in "ps-qa could not attach". The control server
advertises <pid>-<instance>.json in a directory under $TMPDIR; the sweep now
takes the one named for the pid it launched. The directory is matched by
wildcard because its name is due to lose "tauri".
az-gui leaves /tmp/qa-profile-db.lock behind even after a clean SIGTERM drain,
so the sweep refused to start on every second run. A lock whose pid is no
longer alive is stale and is removed; one held by a live process still stops
the sweep.
The chat-spacing change added the import out of order, and bun run lint fails
the Frontend job on it.
tauri-runtime-blitz is published as izumo from 0.4.1, the release that answers
agent-control requests instead of dropping the reply. The dependency, its three
feature forwards, the runtime calls, the crates the owner build checks and the
startup log all follow the new name. blitz-control-protocol moves to ^0.5.3,
the version izumo is built against, so the two resolve to one copy.
The published 0.1.10 no longer compiles against endpoint-libs 3.2, which
removed framed_json_with_max_frame, and the Rust job builds the sidecar from
the resolved release, so every run failed there. 0.1.11 is built against 3.2.
The repository and its local checkout are now izumo; local-renderer.sh
refused to run because it checked for the old directory.
The runtime is published as izumo and its repository is pathscale/izumo. The
docs, the QA notes and a script comment said tauri-runtime-blitz or TRB; they
say izumo, and docs/trb-consumer-history is docs/izumo-consumer-history. Two
lines that name a version only published under the old name keep it with
"(now izumo)".
blitz-preview still took tauri-runtime-blitz 0.3, the crate izumo was renamed
from. It takes izumo ^0.4, and the ps-blitz crates move to ^0.4 with it:
set_document_factory takes izumo's own ScriptDocument, so a second ps-blitz line
would be a second, incompatible type. Every call maps one to one. The README
and a comment that said the control socket was private and unfinished are
brought up to date.
The shorter crate name let rustfmt fold the debug-options call onto one line.
cargo fmt --check failed on six spots: three the izumo rename shortened enough
to fold, three older ones.
izumo, blitz-control-protocol, agent-experimental and promptsyntax were
required at a patch floor. No lockfile is tracked, so the newest release
resolves anyway; the floor only had to be chased on every release.
… client

agent-abstraction 0.5 and agency-proxy 0.1.11 run on nagoya and take the
reactor they register on as an argument rather than starting one of their
own. The app creates the one Reactor during setup, hands its Handle to the
AgencyProxy client and, through list_models, to model discovery, and keeps
the Reactor in AppState for the life of the process: dropping it would stop
the thread that reports those sockets and child processes.

list_models takes the app state now; the catalogue itself is catalogues(),
which the tests call with no reactor because nothing is discovered.
The proxy's event receiver is agency-proxy-client's own broadcast type.
az-gui calls tokio::select! and tokio::join! but never asked for the macros
feature; the old agent-abstraction's tokio switched it on for the whole graph.
With that dependency gone the release build stopped compiling, while the tests
kept passing because the dev-dependency enables macros.
The editor mounts under its row, so a row near the bottom opened it below
the fold: in CI's 1344x900 window the URL field laid out at y=936, focused
but off screen, and `item-issue-editor-opens` failed. Focus moves no
scrollport. Reveal the block holding the row and its editor, then focus.
`sh -c 'kill -INT $$'` does not reliably die of the signal: the shell
catches SIGINT and can reach exit first, and an inherited ignore cannot be
undone. The watcher then saw an ordinary exit and the test failed about
one run in seven. Send SIGINT from the parent to a sleep whose SIGINT
disposition is reset to default across the exec.
@pathscale
pathscale merged commit 3c04578 into master Sep 23, 2026
3 checks passed
@pathscale
pathscale deleted the ci/bound-and-cache-rust-job branch September 23, 2026 17:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant