GitHub Actions CI for the polymarket_fetch Rust workspace. It had been red on every push since 2026-05-19; as of 2026-05-22 it is fully green. This note records what was broken and the fixes — most are reusable gotchas.
The headline lesson
Pin the Rust toolchain in rust-toolchain.toml. A floating channel = "stable" means the CI runner picks up whatever the newest stable is, while the local dev machine sits on an older release. Newer clippy ships new lints, so code passes cargo clippy locally and fails the same check in CI. That skew is exactly why CI “kept failing” for no obvious reason. rust-toolchain.toml is now pinned to 1.95.0 — bumping it is a deliberate, reviewed edit, not a drift.
What was failing
fmt
Unformatted code. Fixed with cargo fmt --all.
clippy
CI ran clippy from the latest stable (1.95.0); the local machine was on 1.91.0. Lints that only exist in newer clippy — use_self, while_let_loop, unnecessary_sort_by — passed locally and failed in CI. Fixed the lints, then pinned rust-toolchain.toml to 1.95.0 so local and CI use an identical compiler and lint set.
cargo-deny
Three separate problems in the deny job:
-
Docker action couldn’t honor the toolchain file.
EmbarkStudios/cargo-deny-action@v2runs in an Alpine/musl container. It readrust-toolchain.tomland tried to use an uninstalledstable-x86_64-unknown-linux-musltarget. Fix: dropped the Docker action. The deny job now runs on a plainubuntu-latestrunner withtaiki-e/install-action@cargo-denyfollowed bycargo deny check. -
Wildcard path dependency flagged. cargo-deny rejected the internal path dependency as a wildcard. Fix: set
allow-wildcard-paths = trueindeny.tomland setpublish = falseon the two binary crates — cargo-deny only permits wildcard path deps when the crate is not published. -
Two RUSTSEC advisories.
rustls-pemfile(unmaintained) andtokio-tar(vuln) were both flagged. Both entered the graph only throughtestcontainersdev-dependencies. Rather than suppress them with an ignore list, bumpedtestcontainers0.23 → 0.27 andtestcontainers-modules0.11 → 0.15, which dropped both crates from the dependency graph entirely.
Docker build
Added a multi-stage Dockerfile that builds the lag-probe binary on top of a slim Debian runtime, plus .github/workflows/docker.yml which builds the image on every push.
Image push is intentionally disabled
docker.ymlruns withpush: false. A registry hasn’t been chosen yet — GHCR vs Google Artifact Registry. Both enablement paths are written into the workflow as comments, ready to uncomment once the call is made.
Local CI gate — scripts/gate.sh
Added scripts/gate.sh (2026-06-03) that runs the full local gate in one shot: build + test + clippy --workspace --all-targets -- -D warnings + fmt. Run it before any push instead of remembering the four commands.
The rtk hook mangles
cargo … -- -D warningsat the top levelrtk’s Claude-Code hook rewrites
cargo clippy --workspace --all-targets -- -D warningsand treats-Das a filename — the clippy check then errors out. The mangling does NOT happen when cargo runs inside a shell script, so wrapping the command inscripts/gate.shbypasses the rewrite entirely. This is the durable workaround for the recurring “rtk passes-Das a path” failure (also noted in correctness-pass-six-fix-commits: run via full pathCARGO=$(which cargo); "$CARGO" clippy …if you must invoke it directly).
The script is allow-listed locally so it no longer prompts for permission on every run.