Skip to content

Fix flaky CI: default SyncOptions, pause test race, job timeout - #119

Merged
Menelion merged 1 commit into
masterfrom
fix/flaky-ci-tests
Sep 10, 2026
Merged

Menelion merged 1 commit into
masterfrom
fix/flaky-ci-tests

Conversation

@Menelion

Copy link
Copy Markdown
Contributor

Summary

Fixes the two intermittent CI failures seen on the recent dependency PRs (#115, #117). Neither was caused by the dependency bumps.

1. Options-less sync ignored SyncOptions defaults (library bug)

SyncEngine stored the options argument as-is, so calling SynchronizeAsync() / SyncFolderAsync() / SyncFilesAsync() without options left _currentOptions null. Every _currentOptions?.X check then fell back to "off", which silently disabled the documented defaults PreserveTimestamps = true and PreservePermissions = true.

This is what made SynchronizeAsync_UpdateExistingFalse_SkipsModifications flaky on macOS: the options-less initial sync left copied files with fresh write timestamps, while the database recorded the scanned timestamps. When the runner took more than the 2-second change-detection window, the next sync saw the remote side as modified too, raised a BothModified conflict, and the UseLocal resolver uploaded, bypassing UpdateExisting = false.

Fix: _currentOptions = options ?? new SyncOptions(); at all three entry points. All other option checks keep their current behaviour, because their defaults match the old null fallback. A regression test (SynchronizeAsync_NoOptions_PreservesTimestampsByDefault) fails without the fix and passes with it.

2. Race in PauseAsync_CalledMultipleTimes_IsIdempotent (test bug, hung ubuntu jobs)

The test spawned a Task.Run per progress event, each calling PauseAsync(), then called ResumeAsync() once. A queued pause could land after that resume, leaving the engine paused with nobody to resume it. All three 6-hour hangs (runs 32477251497, 32477889169, 33693463535) stopped at exactly this test.

Fix: all pause calls now happen synchronously in the first non-scanning progress event, so they always precede the resume. The test waits for either the pause point or sync completion, and caps the final wait at 30 s so a regression fails instead of hanging.

3. CI safety net

timeout-minutes: 30 on the build job, so any future hang fails in 30 minutes instead of 6 hours.

Testing

  • dotnet format --verify-no-changes: clean
  • dotnet test: 819 passed, 130 skipped (integration), 0 failed
  • The three affected tests passed 10/10 consecutive runs locally

Follow-up (not in this PR)

A file that exists on both sides with no sync state produces a concurrent Upload and Download of the same path during the first sync. Content is identical so it's harmless today, but it's a read/write race and should become a single compare-then-record step.

https://claude.ai/code/session_01XugGo6npZqDT15B3EcMkDk

- SyncEngine: fall back to a default SyncOptions when none is passed,
  so documented defaults (PreserveTimestamps, PreservePermissions)
  apply. Without it, files copied by an options-less sync kept fresh
  write timestamps; on slow runners a later sync saw the remote side
  as modified and turned a skipped update into a conflict upload
  (SynchronizeAsync_UpdateExistingFalse_SkipsModifications on macOS).
- Add a regression test for the options-less timestamp default.
- SyncEngineTests: PauseAsync_CalledMultipleTimes_IsIdempotent now
  issues all pause calls before resuming. A late PauseAsync from a
  queued Task.Run could land after the single ResumeAsync and hang the
  ubuntu job until GitHub's 6-hour timeout.
- CI: add timeout-minutes: 30 to the build job.

Claude-Session: https://claude.ai/code/session_01XugGo6npZqDT15B3EcMkDk
@codecov

codecov Bot commented Sep 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 78.82%. Comparing base (40b3582) to head (6d74f84).

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #119      +/-   ##
==========================================
- Coverage   78.99%   78.82%   -0.17%     
==========================================
  Files          44       44              
  Lines        4874     4874              
  Branches      723      726       +3     
==========================================
- Hits         3850     3842       -8     
- Misses        774      775       +1     
- Partials      250      257       +7     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Menelion
Menelion merged commit 2a22419 into master Sep 10, 2026
5 checks passed
@Menelion
Menelion deleted the fix/flaky-ci-tests branch September 10, 2026 20:24
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