From 03ce924e32388b350b5235ed307629e41b518d88 Mon Sep 17 00:00:00 2001 From: Wes Date: Tue, 13 Oct 2026 10:46:31 -0600 Subject: [PATCH] fix(ci): restore green main after merge skew and new advisory (#8277) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Main CI has been red since c09730e87 (#8223). The Mobile and Security checks fail on main, and Docker image fails because it requires a green CI run for the same commit. Locally, the pre-push Rust lane also failed on load-sensitive `buzz-acp` timing tests. This PR fixes all three so main passes locally and on GitHub. - **Mobile:** #8223 was tested against a base from before #8206, which removed `buzzDevPushAppProfile` and app-profile selection. Its new `notification_toggle_test.dart` still put `appProfile` in the native grant map, so the merged tree doesn't compile. This removes the stale field. - **Security:** cargo-deny now reports [RUSTSEC-2024-0449](https://rustsec.org/advisories/RUSTSEC-2024-0449) (`unsafe-libyaml` is unmaintained), which comes in through `serde_yaml`. It's an unmaintained notice, not a vulnerability. The `serde_yaml_ng` fork depends on the same crate, so removing it means migrating YAML parsing in five crates. Like the existing unmaintained entries, it goes in `deny.toml` with a reason. - **buzz-acp test flakes:** the `spawn_script` test helper returned before bash was running, and the timing tests started their idle clock right away. Under full-suite load, process startup took longer than the idle windows. `keepalive_resets_idle_past_deadline`, `idle_resets_on_stdout_activity` and `acp_steer_request_omits_expected_run_id_and_carries_session_and_prompt` failed this way across three pre-push runs. The helper now has the script print a ready line and consumes it before returning. The keepalive loops also use brace expansion instead of `$(seq)`, which avoids an extra process launch before the first line. The keepalive test now uses a 250ms idle window with a 750ms floor. Both tests still fail if keepalives don't reset the idle clock. ### Validation - `cargo-deny check advisories` passes with this `deny.toml` and fails on main's. - The `notification_toggle_test.dart` tests and `flutter analyze` pass. - `cargo test -p buzz-acp --lib` passes all 1055 tests. - The `acp::tests` suite passed 20 of 20 runs with 4 concurrent copies over 5 rounds. Under the same load, the version without the brace-expansion change failed every run. - All pre-push hooks pass on 5dbaa4d32: rust-tests, mobile-checks, desktop-tauri-checks, file-size-check, branch-skew. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: Wes Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz> Co-authored-by: Claude Opus 5.5 --- crates/buzz-acp/src/acp.rs | 40 ++++++++++++++----- deny.toml | 5 +++ .../settings/notification_toggle_test.dart | 1 - 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/crates/buzz-acp/src/acp.rs b/crates/buzz-acp/src/acp.rs index 211c201d60..985b29bee7 100644 --- a/crates/buzz-acp/src/acp.rs +++ b/crates/buzz-acp/src/acp.rs @@ -3198,10 +3198,30 @@ mod tests { ); } + /// Spawn `script` under bash and return once it is running. + /// + /// Process startup can take hundreds of milliseconds on a loaded host, + /// which would otherwise eat into the idle and timing windows the tests + /// measure. The script prints a ready line first, consumed here, so each + /// test's clock starts only after the child is actually executing. async fn spawn_script(script: &str) -> AcpClient { - AcpClient::spawn("bash", &["-c".into(), script.into()], &[], false) + const READY: &str = "buzz-acp-test-script-ready"; + let mut client = AcpClient::spawn( + "bash", + &["-c".into(), format!("echo {READY}; {script}")], + &[], + false, + ) + .await + .expect("failed to spawn test script"); + let ready = tokio::time::timeout(std::time::Duration::from_secs(10), client.reader.next()) .await - .expect("failed to spawn test script") + .expect("test script should start within 10s"); + assert!( + matches!(&ready, Some(Ok(line)) if line == READY), + "expected ready line, got {ready:?}" + ); + client } #[cfg(unix)] @@ -3367,7 +3387,7 @@ mod tests { // Send valid JSON (session/update notifications) to reset the idle timer. // Non-JSON lines no longer reset idle — only valid JSON notifications do. let mut client = spawn_script( - r#"for i in $(seq 1 10); do echo '{"jsonrpc":"2.0","method":"session/update","params":{"update":{"sessionUpdate":"agent_thought_chunk","content":{"text":"thinking"}}}}'; sleep 0.05; done; sleep 10"#, + r#"for i in {1..10}; do echo '{"jsonrpc":"2.0","method":"session/update","params":{"update":{"sessionUpdate":"agent_thought_chunk","content":{"text":"thinking"}}}}'; sleep 0.05; done; sleep 10"#, ) .await; let max_dur = std::time::Duration::from_secs(10); @@ -3561,10 +3581,10 @@ mod tests { #[tokio::test] async fn keepalive_resets_idle_past_deadline() { - // Keepalive session/update lines every 50ms against a 100ms idle deadline. - // The turn should survive well past the 100ms deadline (proves the fix). + // Keepalive session/update lines every 50ms against a 250ms idle deadline. + // The turn should survive well past the idle deadline (proves the fix). let mut client = spawn_script( - r#"for i in $(seq 1 20); do echo '{"jsonrpc":"2.0","method":"session/update","params":{"update":{"sessionUpdate":"keepalive"}}}'; sleep 0.05; done; sleep 10"#, + r#"for i in {1..20}; do echo '{"jsonrpc":"2.0","method":"session/update","params":{"update":{"sessionUpdate":"keepalive"}}}'; sleep 0.05; done; sleep 10"#, ) .await; let max_dur = std::time::Duration::from_secs(10); @@ -3574,16 +3594,16 @@ mod tests { .read_until_response_with_idle_timeout( "test", 999, - std::time::Duration::from_millis(100), + std::time::Duration::from_millis(250), hard_deadline, max_dur, ) .await; let elapsed = start.elapsed(); - // 20 keepalives × 50ms = ~1000ms of activity, then idle fires after 100ms more. - // Must survive well past the 100ms deadline. + // 20 keepalives × 50ms = ~1000ms of activity, then idle fires after 250ms more. + // Must survive well past the 250ms deadline. assert!( - elapsed >= std::time::Duration::from_millis(500), + elapsed >= std::time::Duration::from_millis(750), "keepalive should reset idle past the deadline; elapsed only {elapsed:?}" ); assert!(elapsed < std::time::Duration::from_secs(5)); diff --git a/deny.toml b/deny.toml index d3c5fcd4bc..74e83fb351 100644 --- a/deny.toml +++ b/deny.toml @@ -15,6 +15,11 @@ ignore = [ # remove these when upstream catches up. { id = "RUSTSEC-2026-0194", reason = "transitive via rust-s3 and mesh-llm→plist; trusted-input XML only; no upstream fix available yet" }, { id = "RUSTSEC-2026-0195", reason = "transitive via rust-s3 and mesh-llm→plist; trusted-input XML only; no upstream fix available yet" }, + # unsafe-libyaml 0.2.11 — unmaintained (repository archived 2024-03-25), not a + # vulnerability report. Transitive dep: serde_yaml → unsafe-libyaml. The serde_yaml_ng + # fork uses the same crate, so removing it means migrating YAML parsing in + # buzz-agent, buzz-persona, buzz-relay, buzz-workflow and desktop. + { id = "RUSTSEC-2024-0449", reason = "transitive via serde_yaml; unmaintained, not vulnerable; removal needs a YAML parser migration" }, ] [licenses] diff --git a/mobile/test/features/settings/notification_toggle_test.dart b/mobile/test/features/settings/notification_toggle_test.dart index 39e46ad9e8..19c80e2522 100644 --- a/mobile/test/features/settings/notification_toggle_test.dart +++ b/mobile/test/features/settings/notification_toggle_test.dart @@ -78,7 +78,6 @@ void main() { 'installationId': 'b' * 32, 'endpointGrant': 'test-endpoint-grant', 'endpointHash': 'c' * 64, - 'appProfile': buzzDevPushAppProfile, 'endpointEpoch': 1, 'generation': 1, 'expiresAt': DateTime.now().millisecondsSinceEpoch ~/ 1000 + 3600,