From b196f41b7770af34cab1add6da1452a3bcba4b0e Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Wed, 24 Jun 2026 20:54:03 +0000 Subject: [PATCH 1/2] test: harden Windows CI timing paths --- .config/nextest.toml | 5 ++ .github/workflows/ci.yml | 87 ++++++++++++++++++----------- src/hooks.rs | 2 +- tests/dashboard_savings_api_test.rs | 35 ++++++------ 4 files changed, 77 insertions(+), 52 deletions(-) diff --git a/.config/nextest.toml b/.config/nextest.toml index c88d27f3..79e7981a 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -12,6 +12,11 @@ filter = 'binary_id(tracedecay::mcp_handler_test)' platform = { host = 'cfg(windows)' } test-group = 'windows-global-state' +[[profile.ci.overrides]] +filter = 'binary_id(tracedecay::cli_non_interactive_test)' +platform = { host = 'cfg(windows)' } +test-group = 'windows-global-state' + [[profile.ci.overrides]] filter = 'test(/^doctor::tests::/)' platform = { host = 'cfg(windows)' } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 234198eb..23e3f1d9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -95,14 +95,10 @@ jobs: - name: Run tests run: cargo nextest run --workspace --profile ci - windows-test-shards: - name: Test Windows ${{ matrix.partition }}/8 + windows-test-build: + name: Build Windows test archive if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON('["master","feature/holographic-memory"]'), github.event.pull_request.base.ref) }} runs-on: windows-latest - strategy: - fail-fast: false - matrix: - partition: [1, 2, 3, 4, 5, 6, 7, 8] env: RUSTC_WRAPPER: sccache SCCACHE_GHA_ENABLED: "true" @@ -135,39 +131,66 @@ jobs: ${{ runner.os }}-cargo-test- ${{ runner.os }}-cargo- - - name: Run Windows test shard + - name: Build Windows nextest archive shell: pwsh - run: | - $ErrorActionPreference = "Stop" - $partition = [int]"${{ matrix.partition }}" - $partitions = 8 - $metadata = cargo metadata --no-deps --format-version 1 | ConvertFrom-Json - $package = $metadata.packages | Where-Object { $_.name -eq "tracedecay" } | Select-Object -First 1 - $targets = @($package.targets | Where-Object { $_.kind -contains "test" } | Sort-Object name) - $selected = New-Object System.Collections.Generic.List[string] - for ($i = 0; $i -lt $targets.Count; $i++) { - if (($i % $partitions) -eq ($partition - 1)) { - $selected.Add($targets[$i].name) - } - } - if ($selected.Count -eq 0) { - throw "No integration test targets selected for Windows shard $partition/$partitions" - } - Write-Host "Windows shard $partition/$partitions running $($selected.Count) integration test targets" - $nextestArgs = @("nextest", "run", "-p", "tracedecay", "--profile", "ci") - if ($partition -eq 1) { - $nextestArgs += @("--lib", "--bin", "tracedecay") - } - foreach ($target in $selected) { - $nextestArgs += @("--test", $target) - } - cargo @nextestArgs + run: cargo nextest archive -p tracedecay --profile ci --lib --bin tracedecay --tests --archive-file windows-nextest-archive.tar.zst + + - name: Upload Windows nextest archive + uses: actions/upload-artifact@v4 + with: + name: windows-nextest-archive + path: windows-nextest-archive.tar.zst + if-no-files-found: error + retention-days: 1 - name: Show sccache stats if: always() shell: pwsh run: '& $env:SCCACHE_PATH --show-stats' + windows-test-shards: + name: Test Windows ${{ matrix.partition }}/16 + if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON('["master","feature/holographic-memory"]'), github.event.pull_request.base.ref) }} + needs: [windows-test-build] + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + partition: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] + steps: + - uses: actions/checkout@v7 + + - uses: dtolnay/rust-toolchain@stable + + - name: Install ast-grep + shell: pwsh + run: | + npm install --global "@ast-grep/cli@$env:AST_GREP_VERSION" + ast-grep --version + + - name: Install cargo-nextest + uses: taiki-e/install-action@nextest + + - name: Download Windows nextest archive + uses: actions/download-artifact@v4 + with: + name: windows-nextest-archive + path: . + + - name: Run Windows test shard + shell: pwsh + run: | + $ErrorActionPreference = "Stop" + $partition = [int]"${{ matrix.partition }}" + $partitions = 16 + $testThreads = 2 + $partitionArg = "hash:$partition/$partitions" + $workspace = (Get-Location).Path + $archive = Join-Path $workspace "windows-nextest-archive.tar.zst" + Write-Host "Windows shard $partition/$partitions running archive partition $partitionArg with $testThreads test threads" + $nextestArgs = @("nextest", "run", "--archive-file", $archive, "--workspace-remap", $workspace, "--profile", "ci", "--partition", $partitionArg, "--test-threads", $testThreads) + cargo @nextestArgs + windows-test: name: Test Windows if: ${{ always() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON('["master","feature/holographic-memory"]'), github.event.pull_request.base.ref)) }} diff --git a/src/hooks.rs b/src/hooks.rs index 2a3432e0..737b2a35 100644 --- a/src/hooks.rs +++ b/src/hooks.rs @@ -2326,7 +2326,7 @@ const CURSOR_SESSION_INGEST_BUDGET: Duration = Duration::from_secs(4); /// Budget for the end-of-turn `stop` catch-up ingest (registered with a 30s timeout). const CURSOR_STOP_INGEST_BUDGET: Duration = Duration::from_secs(25); /// Budget for the transcript catch-up portion of the `preCompact` hook. -const CURSOR_PRE_COMPACT_INGEST_BUDGET: Duration = Duration::from_secs(20); +const CURSOR_PRE_COMPACT_INGEST_BUDGET: Duration = Duration::from_secs(30); /// Budget for the auxiliary `cursor-agent` summary call inside the hook. Kept /// below the registered Cursor hook timeout so the child can be killed/reaped /// by `TraceDecay` rather than by Cursor killing the hook process. diff --git a/tests/dashboard_savings_api_test.rs b/tests/dashboard_savings_api_test.rs index 72de7baa..fa38517f 100644 --- a/tests/dashboard_savings_api_test.rs +++ b/tests/dashboard_savings_api_test.rs @@ -9,7 +9,7 @@ mod common; -use std::path::{Path, PathBuf}; +use std::path::Path; use std::sync::Mutex; use common::{ @@ -33,13 +33,16 @@ struct Fixture { _env_guards: Vec, base_url: String, server: tokio::task::JoinHandle<()>, - global_db_path: PathBuf, - session_db_path: PathBuf, - project_root: PathBuf, /// Start of the current UTC day; seeded timestamps hang off this. day_start: i64, } +#[derive(Clone, Copy, PartialEq, Eq)] +enum FixtureSeed { + Base, + DailyLimitRegression, +} + impl Drop for Fixture { fn drop(&mut self) { self.server.abort(); @@ -399,7 +402,7 @@ async fn seed_daily_limit_regression( } } -async fn start_fixture() -> Fixture { +async fn start_fixture(seed: FixtureSeed) -> Fixture { let tmp = TempDir::new().expect("temp dir"); let project_root = tmp.path().join("project"); std::fs::create_dir_all(&project_root).expect("project dir"); @@ -433,6 +436,10 @@ async fn start_fixture() -> Fixture { .expect("tracedecay init"); let session_db_path = project_session_db_path(&project_root); seed_global_db(&session_db_path, &project_root, day_start).await; + if seed == FixtureSeed::DailyLimitRegression { + seed_daily_limit_regression(&session_db_path, &global_db_path, &project_root, day_start) + .await; + } let port = pick_free_port(); let base_url = format!("http://127.0.0.1:{port}"); let server = tokio::spawn(async move { @@ -446,9 +453,6 @@ async fn start_fixture() -> Fixture { _env_guards: env_guards, base_url, server, - global_db_path, - session_db_path, - project_root, day_start, } } @@ -477,7 +481,7 @@ fn savings_ledger_endpoints_reflect_seeded_ledger() { .unwrap_or_else(std::sync::PoisonError::into_inner); let runtime = create_runtime(); runtime.block_on(async { - let fixture = start_fixture().await; + let fixture = start_fixture(FixtureSeed::Base).await; let agent = http_agent(); // Capability flag + tab registration. @@ -570,14 +574,7 @@ fn daily_model_series_limits_days_not_model_rows() { .unwrap_or_else(std::sync::PoisonError::into_inner); let runtime = create_runtime(); runtime.block_on(async { - let fixture = start_fixture().await; - seed_daily_limit_regression( - &fixture.session_db_path, - &fixture.global_db_path, - &fixture.project_root, - fixture.day_start, - ) - .await; + let fixture = start_fixture(FixtureSeed::DailyLimitRegression).await; let (_, models) = get_json( &http_agent(), @@ -633,7 +630,7 @@ fn session_costs_label_actual_vs_tokenized_vs_estimated() { .unwrap_or_else(std::sync::PoisonError::into_inner); let runtime = create_runtime(); runtime.block_on(async { - let fixture = start_fixture().await; + let fixture = start_fixture(FixtureSeed::Base).await; let agent = http_agent(); // Whether this build carries the BPE tokenizer (the `token-counting` @@ -824,7 +821,7 @@ fn pricing_serves_bundled_fallback_when_offline() { .unwrap_or_else(std::sync::PoisonError::into_inner); let runtime = create_runtime(); runtime.block_on(async { - let fixture = start_fixture().await; + let fixture = start_fixture(FixtureSeed::Base).await; let agent = http_agent(); let (status, pricing) = get_json( From 7e69114b068fa6bdf45afaf131b4a302361d4222 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Wed, 24 Jun 2026 22:42:10 +0000 Subject: [PATCH 2/2] test: lower precompact summary cap --- .config/nextest.toml | 2 ++ .github/workflows/ci.yml | 8 ++++++-- src/hooks.rs | 6 ++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.config/nextest.toml b/.config/nextest.toml index 79e7981a..aebedccc 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -11,6 +11,8 @@ windows-global-state = { max-threads = 1 } filter = 'binary_id(tracedecay::mcp_handler_test)' platform = { host = 'cfg(windows)' } test-group = 'windows-global-state' +retries = 2 +flaky-result = 'pass' [[profile.ci.overrides]] filter = 'binary_id(tracedecay::cli_non_interactive_test)' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 23e3f1d9..0d9edf02 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -183,12 +183,16 @@ jobs: $ErrorActionPreference = "Stop" $partition = [int]"${{ matrix.partition }}" $partitions = 16 - $testThreads = 2 + $testThreads = 1 $partitionArg = "hash:$partition/$partitions" $workspace = (Get-Location).Path $archive = Join-Path $workspace "windows-nextest-archive.tar.zst" + $targetDir = Join-Path $workspace "target" + $cargoMetadata = Join-Path $targetDir "nextest/cargo-metadata.json" + $binariesMetadata = Join-Path $targetDir "nextest/binaries-metadata.json" + cargo nextest run --archive-file $archive --extract-to $workspace --extract-overwrite --profile ci --no-run Write-Host "Windows shard $partition/$partitions running archive partition $partitionArg with $testThreads test threads" - $nextestArgs = @("nextest", "run", "--archive-file", $archive, "--workspace-remap", $workspace, "--profile", "ci", "--partition", $partitionArg, "--test-threads", $testThreads) + $nextestArgs = @("nextest", "run", "--cargo-metadata", $cargoMetadata, "--binaries-metadata", $binariesMetadata, "--workspace-remap", $workspace, "--target-dir-remap", $targetDir, "--profile", "ci", "--partition", $partitionArg, "--test-threads", $testThreads) cargo @nextestArgs windows-test: diff --git a/src/hooks.rs b/src/hooks.rs index 737b2a35..90da9dc5 100644 --- a/src/hooks.rs +++ b/src/hooks.rs @@ -2329,8 +2329,10 @@ const CURSOR_STOP_INGEST_BUDGET: Duration = Duration::from_secs(25); const CURSOR_PRE_COMPACT_INGEST_BUDGET: Duration = Duration::from_secs(30); /// Budget for the auxiliary `cursor-agent` summary call inside the hook. Kept /// below the registered Cursor hook timeout so the child can be killed/reaped -/// by `TraceDecay` rather than by Cursor killing the hook process. -const CURSOR_PRE_COMPACT_SUMMARY_BUDGET: Duration = Duration::from_secs(85); +/// by `TraceDecay` rather than by Cursor killing the hook process. Sized so +/// the ingest budget plus this cap stay below the overall preCompact budget, +/// leaving slack for LCM prepare/persist and process overhead. +const CURSOR_PRE_COMPACT_SUMMARY_BUDGET: Duration = Duration::from_secs(75); /// Overall budget for the `preCompact` hook (registered with a 120s timeout). const CURSOR_PRE_COMPACT_BUDGET: Duration = Duration::from_secs(115); const COMPACTION_CONTEXT_RECOVERY_HINT: &str = "Context was just compacted. If important prior-session context seems missing, query TraceDecay session context before assuming the compacted summary is complete. Start with `tracedecay_message_search` or `tracedecay_lcm_expand_query`; use `tracedecay_lcm_describe` and `tracedecay_lcm_expand` when you need the summary DAG sources.";