Skip to content

ci: cut the Windows build's redundant pass, Defender scanning, and PDB cost - #349

Merged
getappz merged 3 commits into
masterfrom
ci/windows-speedup
Jul 27, 2026
Merged

ci: cut the Windows build's redundant pass, Defender scanning, and PDB cost#349
getappz merged 3 commits into
masterfrom
ci/windows-speedup

Conversation

@getappz

@getappz getappz commented Jul 27, 2026

Copy link
Copy Markdown
Owner

The Windows leg of build takes ~3x the Ubuntu one. Per-step timings from run 30237145340:

step Windows Ubuntu
rust-cache restore 54s 12s
sccache setup 17s 1s
cargo build --workspace 1m33s 16s
cargo test --workspace 3m16s 1m18s
total 6m10s 1m56s

Inside the Windows test step: ~60s compiling test harnesses, then ~91s purely executing the 835-test agentflare suite (04:37:58 → 04:39:29 in the log). Three changes, each aimed at one of those numbers.

1. Drop the separate cargo build --workspace step

cargo test --workspace compiles and links the same libs and bins — it builds every bin target so integration tests can spawn them, which is exactly what crates/flare-git-shim/tests/shim_test.rs relies on via env!("CARGO_BIN_EXE_git"). Building first was a second full pass over the workspace for nothing.

Nothing is lost in coverage: clippy --locked --workspace --all-targets already type-checks the non-test targets, and the bins are still linked by the test step. Saves 1m33s on Windows, 16s on Ubuntu, deterministically.

2. Defender exclusions on Windows runners

The windows-latest image has real-time protection on. It scans every object file, PDB and executable rustc writes — and every temp file the tests create, of which this suite creates a great many (tempdirs, SQLite files, spawned binaries). That is the likeliest reason the same tests take 91s there.

Best-effort by construction: a runner that refuses the exclusion should build slowly, not fail the job, so the Add-MpPreference call is wrapped in try/catch.

3. CARGO_PROFILE_DEV_DEBUG=line-tables-only

Full debuginfo costs twice: MSVC spends real link time writing PDBs, and those artifacts are what the rust-cache archive has to restore on every run — which is the 54s against 12s in the table. Nothing in CI attaches a debugger, and line-tables-only keeps panic backtraces readable, which is the only debug information a failing job actually uses.

Set at workflow level rather than per job so build and clippy produce matching artifacts; divergent profiles between them would halve the cache hit rate. Expect one round of cache misses until master rebuilds its cache under the new setting.

Also: de-flake agentflare-artifacts::update_existing_artifact

This failed on a windows-latest runner today (left: 1785126773, right: 1785126774) and cost a full rerun of #348. It asserted:

// created_at must stay the same on update
assert_eq!(artifact.created_at, artifact.updated_at); // same sec

Two problems, not one. It fails whenever the two publishes straddle a second boundary — and it was passing for the wrong reason: I confirmed the same assertion still passes with created_at deliberately rewritten on update, because within one second the two values coincide either way. So the invariant it documents was never actually tested.

Simply comparing against the previously stored created_at inherits the same blind spot, so the test now backdates the stored value first, which makes preserved-vs-restamped observable regardless of clock:

let meta_path = store.base_path().join(&id).join("meta.json");
let created_before = store.get(&id).unwrap().created_at - 3600;
let meta = std::fs::read_to_string(&meta_path).unwrap();
let backdated = meta.replace(
    &format!("\"created_at\": {}", created_before + 3600),
    &format!("\"created_at\": {created_before}"),
);
assert_ne!(meta, backdated, "created_at not found in {meta_path:?}");
std::fs::write(&meta_path, backdated).unwrap();
// ... publish the update ...
assert_eq!(artifact.created_at, created_before);
assert!(artifact.updated_at >= artifact.created_at);

Verified both ways: passes as-is, and fails when store.rs's created_at: prev.as_ref().map(|m| m.created_at).unwrap_or(now) is mutated to created_at: now.

Verification

cargo fmt --all --check passes and the artifacts test was run and mutation-checked locally. The full local cargo test --workspace could not be completed — this machine's disk hit 0 bytes free partway through (it surfaced as spurious only metadata stub found for rlib dependency core errors, which are disk-exhaustion in disguise). CI is the verification for this one, which is fitting given what the PR changes; the workflow YAML was parsed and the resulting build step list checked before pushing.

Worth watching on this run: whether build (windows-latest) drops meaningfully below 6m, and whether the first post-merge master run repopulates the cache cleanly under the new debug setting.

Summary by CodeRabbit

  • Bug Fixes

    • Improved reliability of artifact update timestamp handling by making creation and update times deterministic, ensuring original creation times are preserved while update times remain accurate.
  • Build & Testing

    • Enhanced CI behavior for Windows builds by conditionally attempting to add security exclusions for common build directories (failure tolerated).
    • Streamlined CI verification by consolidating compilation and verification into the test run, improving performance and reliability.

…B cost

Measured on run 30237145340: Windows 6m10s against Ubuntu's 1m56s, split
as rust-cache restore 54s / 12s, cargo build 1m33s / 16s, cargo test
3m16s / 1m18s. Inside the Windows test step, ~91s is spent purely
executing the 835-test suite.

Three things, each aimed at one of those numbers:

The separate cargo build step is gone. cargo test compiles and links the
same libs and bins -- it builds every bin target so integration tests can
spawn them, which is what crates/flare-git-shim/tests/shim_test.rs relies
on -- so building first was a second full pass over the workspace for
nothing. Non-test targets stay covered by clippy --all-targets.

Windows runners get Defender exclusions for the workspace, the cargo home
and TEMP. Real-time protection is on in that image and scans every object
file, PDB and executable rustc writes, plus every temp file the tests
create -- the likeliest reason the same suite takes 91s there. Best
effort: a runner that refuses the exclusion builds slowly rather than
failing.

CARGO_PROFILE_DEV_DEBUG=line-tables-only workflow-wide. Nothing in CI
attaches a debugger, MSVC spends real link time writing PDBs, and those
artifacts are what the cache archive restores every run -- which is the
54s against 12s. Panic backtraces stay readable. Workflow-level so build
and clippy produce matching artifacts instead of halving the hit rate.

Also de-flakes agentflare-artifacts::update_existing_artifact, which
failed this way on a windows-latest runner today (1785126773 against
1785126774) and forced a rerun. It asserted created_at == updated_at,
true only while both publishes land in the same second -- and passing for
that reason rather than because the invariant held: the same assertion
still passed with created_at deliberately rewritten on update. It now
backdates the stored value first and checks the update carried it
forward, which does fail under that mutation.
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The CI workflow adjusts Rust debug settings, Windows build preparation, and test compilation behavior. The artifact update test backdates persisted creation metadata and verifies creation-time preservation and update-time ordering.

Changes

CI workflow updates

Layer / File(s) Summary
Build and test workflow
.github/workflows/ci.yml
The workflow configures line-table-only Rust debug output, documents runner-controlled paths, adds best-effort Windows Defender exclusions, and relies on cargo test instead of a separate build step.

Artifact timestamp test

Layer / File(s) Summary
Deterministic timestamp assertions
crates/agentflare-artifacts/src/lib.rs
The test backdates persisted created_at metadata before updating an artifact, then verifies that created_at is preserved and updated_at is no earlier.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main CI change: reducing Windows build overhead with a redundant pass, Defender scanning, and PDB costs.
Description check ✅ Passed It covers the PR summary and verification, but it does not follow the template’s exact Summary/Test plan/Notes headings or checklist format.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci/windows-speedup

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/agentflare-artifacts/src/lib.rs`:
- Around line 87-90: Update the backdating logic around meta.replace so only the
root artifact-level created_at field is modified, leaving history[0].created_at
and other nested fields unchanged. Parse and update the root JSON property
directly, then serialize it while preserving the existing created_before value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 71a8ccfc-fcb8-4b8c-9d3d-742119b51888

📥 Commits

Reviewing files that changed from the base of the PR and between fc11f59 and 83bcc3b.

📒 Files selected for processing (2)
  • .github/workflows/ci.yml
  • crates/agentflare-artifacts/src/lib.rs

Comment thread crates/agentflare-artifacts/src/lib.rs Outdated
…tory

The artifact's created_at and its first history entry's start out
identical, so replacing the timestamp textually moved both -- leaving the
fixture describing a version created an hour before the artifact it
belongs to. Edit the root JSON field instead.
@getappz

getappz commented Jul 27, 2026

Copy link
Copy Markdown
Owner Author

Fixed in b57e916 — you're right, and the fixture was describing a version created an hour before the artifact it belongs to. Switched to editing the root field as JSON:

-        let created_before = store.get(&id).unwrap().created_at - 3600;
-        let meta = std::fs::read_to_string(&meta_path).unwrap();
-        let backdated = meta.replace(
-            &format!("\"created_at\": {}", created_before + 3600),
-            &format!("\"created_at\": {created_before}"),
-        );
-        assert_ne!(meta, backdated, "created_at not found in {meta_path:?}");
-        std::fs::write(&meta_path, backdated).unwrap();
+        let mut meta: serde_json::Value =
+            serde_json::from_str(&std::fs::read_to_string(&meta_path).unwrap()).unwrap();
+        let created_before = meta["created_at"]
+            .as_u64()
+            .unwrap_or_else(|| panic!("no created_at in {meta_path:?}"))
+            - 3600;
+        meta["created_at"] = created_before.into();
+        std::fs::write(&meta_path, serde_json::to_string_pretty(&meta).unwrap()).unwrap();

Still passes, and still fails under the created_at: prev.as_ref().map(|m| m.created_at).unwrap_or(now)created_at: now mutation, which is the point of the backdating.


On the timing evidence, being straight about it: this run does not measure the change.

Windows step-by-step, baseline run 30237145340 against run 30244129771 on this branch:

step baseline this PR
rust-cache restore 54s 7s
sccache setup 17s 7s
Defender exclusions 4s
cargo build --workspace 1m33s (removed)
cargo test --workspace 3m16s 7m44s
total 6m10s 8m13s

The 54s → 7s restore is not a faster restore, it is a miss: CARGO_PROFILE_DEV_DEBUG changes the cache key, so there was nothing on master to restore and the test step compiled the workspace cold. That also explains 3m16s → 7m44s, and Ubuntu's 1m56s → 4m05s.

And it will stay that way on this branch: save-if: github.ref == 'refs/heads/master' means PR runs never write cache, so every rerun here re-pays the cold build. The first master run after merge is likewise cold; the run after that is the first honest measurement.

So what this run actually establishes is that the workspace still builds and every test passes with the cargo build step gone — which is the part of the change that needed proving, since the rest is cache and scanner behaviour that only shows up warm.

Local gate on this branch is now green end to end (cargo fmt --check, the CI clippy invocation, cargo test --workspace) — the earlier PR body noted that was blocked by a full disk on my side; that is resolved.

@getappz
getappz enabled auto-merge (squash) July 27, 2026 07:13
@getappz
getappz merged commit f58dff0 into master Jul 27, 2026
16 checks passed
@getappz
getappz deleted the ci/windows-speedup branch July 27, 2026 07:21
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