Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# ci-nuget-cache-fallback-masks-stale-package-refs (Issue #569)

- Date captured: 2026-08-15
- Author: Dan Moisan
- Status: Promoted -> docs/features/active/ci-nuget-cache-fallback-masks-stale-package-refs/ (Issue #569)

> Automation note: Keep the section headings below unchanged; the promotion tooling maps each of them into the GitHub bug issue template.

- Issue: #569
- Issue URL: https://github.com/drmoisan/TaskMaster/issues/569
- Last Updated: 2026-08-15
## Summary

The three build and test workflows restore the `packages` directory from an
`actions/cache` entry whose `restore-keys` fallback is a bare
`nuget-${{ runner.os }}-` prefix. On a cache miss the fallback supplies package
folders from an unrelated earlier commit, so a project referencing a package
version that `packages.config` no longer declares still compiles. Two real
defects on PR #568 were invisible to CI for exactly this reason and only
appeared on a local cold build.

## Environment

- OS/version: `windows-latest` GitHub-hosted runner
- Python version: n/a (GitHub Actions YAML)
- Command/flags used: `nuget restore TaskMaster.sln`, then the workflow's msbuild or vstest step
- Data source or fixture: `actions/cache@v4` entry keyed on `**/packages.config`

## Steps to Reproduce

1. On a branch, change a package version in every `packages.config` but leave a
stale `..\packages\<Id>.<OldVersion>\` path in a `.csproj` or in test code.
2. Push. The cache key `nuget-${{ runner.os }}-${{ hashFiles('**/packages.config') }}`
misses because the config files changed.
3. The `restore-keys: nuget-${{ runner.os }}-` fallback restores a prior cache
that still contains `<Id>.<OldVersion>`.
4. `nuget restore` installs the new versions alongside the stale restored ones.
5. The build and the tests pass, despite referencing a version no longer
declared anywhere in the repository.

## Expected Behavior

CI reflects what a clean checkout produces. A project or test that references a
package version absent from `packages.config` fails the build, because that is
what any developer with a cold `packages` directory experiences.

## Actual Behavior

CI run 31890892701 on `chore/update-nuget` head `8f30fd53` reported
`build-analyzers`, `build-nullable`, and `mstest-coverage` as **success**. The
same commit built locally against a correctly-restored `packages` directory
produced:

- 10 `CSC : error CS0006: Metadata file '..\packages\Meziantou.Analyzer.3.0.138\...'
could not be found` errors (and the matching `Roslynator.Analyzers.4.15.0`
errors) across 16 projects, because the upgrade advanced `packages.config` and
the analyzer `<Import>`/`<Error>` lines to 3.0.156 / 4.16.0 but left the
`<Analyzer Include>` item paths behind.
- 3 test failures in `UtilitiesCS.Test.Extensions.AsyncSerialization_Tests`
(`InvalidOperationException: The Microsoft.Graph.xml fixture could not be
located from the test assembly path`), because the fixture hard-coded
`packages\Microsoft.Graph.6.2.0` while the upgrade moved to 6.5.0.

Only the `format-check` job failed, and it is the one job with no `packages`
cache dependency.

## Logs / Screenshots

- [x] Attached minimal logs or screenshot
- Snippet: CI run 31890892701 job list showing three green build/test jobs on the
commit that fails a cold local build; local msbuild CS0006 output and the TRX
failure messages quoted above. Both defects were fixed in commit `46ca9210`.

## Impact / Severity

- [ ] Blocker
- [x] High
- [ ] Medium
- [ ] Low

High because it is a gate-fidelity defect, not an ordinary bug: it caused three
protected checks to report green on a commit that does not build from a clean
checkout. Any future package-path drift is silently admitted to `main` the same
way. It also affects the correctness signal of every prior green run whose cache
was populated from an older commit.

## Suspected Cause / Notes

- Affected workflows, all with the same cache block:
- `.github/workflows/_build-analyzers.yml` (lines 35-45)
- `.github/workflows/_build-nullable.yml`
- `.github/workflows/_mstest-coverage.yml`
- The exact-match key is correct; the `restore-keys` prefix fallback is what
admits foreign content. For a `packages.config` restore the cache is only
sound as an exact match, because `nuget restore` adds missing packages but
never removes packages the current configuration does not declare.
- Two independent remedies exist and are complementary:
1. Drop the `restore-keys` fallback so a `packages.config` change forces a
cold restore. Costs restore time on dependency changes only.
2. Add a validation step asserting that every `..\packages\<Id>.<Version>\`
path referenced by a `.csproj` resolves to a version declared in that
project's `packages.config`. This catches drift regardless of cache state
and is the stronger of the two.
- Note that remedy 2 must tolerate genuinely conditional imports: for example
`QuickFiler.Test.csproj` imports `..\packages\altcover.8.6.45\...` guarded by
`Condition="Exists(...)"` and is a pre-existing no-op, not a defect.
- Related repository rule: `.claude/rules/ci-workflows.md` already governs
workflow-authoring hazards that local toolchain stages cannot see. This finding
is the same class of problem and likely belongs alongside it once fixed.

## Proposed Fix / Validation Ideas

- [ ] Unit coverage areas: if remedy 2 is implemented as a script, it needs
Pester coverage per `.claude/rules/general-unit-test.md`, including a
positive case, a stale-reference case, and a conditional-import case.
- [ ] Integration scenario to retest: reproduce the original failure by pushing
a branch with a deliberately stale `<Analyzer Include>` path and confirming
CI now fails.
- [ ] Manual verification notes: confirm a clean-cache run still completes within
the workflows' 30-minute timeouts.

## Next Step

- [x] Promote to GitHub issue (bug-report template)
- [ ] Move to active fix folder / branch
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# qfc-item-controller-init-tests-flaky-window-handle (Issue #571)

- Date captured: 2026-08-15
- Author: Dan Moisan
- Status: Promoted -> docs/features/active/qfc-item-controller-init-tests-flaky-window-handle/ (Issue #571)

> Automation note: Keep the section headings below unchanged; the promotion tooling maps each of them into the GitHub bug issue template.

- Issue: #571
- Issue URL: https://github.com/drmoisan/TaskMaster/issues/571
- Last Updated: 2026-08-15
## Summary

Two tests in `QuickFiler.Controllers.Tests.QfcItemController_InitializationTests`
fail intermittently during a full-suite run with
`InvalidOperationException: Invoke or BeginInvoke cannot be called on a control
until the window handle has been created`, but pass every time when the class is
run in isolation. The tests exercise a real WinForms `Control.Invoke` path with
no seam, so they depend on whether the control's window handle happens to exist
when the test reaches it.

## Environment

- OS/version: Windows 11 Pro 10.0.26200
- Python version: n/a (C# / .NET Framework 4.8.1, MSTest)
- Command/flags used: `vstest.console.exe <9 test assemblies> /EnableCodeCoverage /InIsolation /Logger:trx /TestCaseFilter:"TestCategory!=LiveOutlook"`
- Data source or fixture: none; the failure is in test-host state, not data

## Steps to Reproduce

1. Build the solution in Debug.
2. Run the full suite across all nine `*.Test.dll` assemblies with the command
above.
3. Repeat. The two tests below fail on some runs and pass on others.

Observed on 2026-08-15: run 1 passed both tests, run 2 failed both, run 3 (full
suite) passed both. Running only
`/TestCaseFilter:"FullyQualifiedName~QfcItemController_InitializationTests"`
passed 9 of 9 on every attempt.

## Expected Behavior

Per `.claude/rules/general-unit-test.md`, tests are deterministic: identical
inputs and environment produce identical results, and the suite does not depend
on ordering or on ambient UI state.

## Actual Behavior

Both of the following fail non-deterministically:

- `InitializeNineArgOverload_ThroughThePumpHost_SavesParametersAndDelegates`
- `InitializeBool_ThroughThePumpHost_CompletesAndInitializesState`

```
System.InvalidOperationException: Invoke or BeginInvoke cannot be called on a
control until the window handle has been created.
at System.Windows.Forms.Control.MarshaledInvoke(...)
at System.Windows.Forms.Control.Invoke(Delegate method, Object[] args)
at QuickFiler.ItemViewer.QuickFiler.IItemViewer.Invoke(Delegate method)
at QuickFiler.Controllers.QfcItemController.InvokeBeginInvoke(Boolean async, Action action)
in QuickFiler\Controllers\QfcItemController.FocusAndTheme.cs:line 256
at QuickFiler.Controllers.QfcItemController.ToggleTips(Boolean async, ToggleState desiredState)
in QuickFiler\Controllers\QfcItemController.FocusAndTheme.cs:line 204
```

## Logs / Screenshots

- [x] Attached minimal logs or screenshot
- Snippet: stack trace above, extracted from the TRX of the failing run on
2026-08-15.

## Impact / Severity

- [ ] Blocker
- [ ] High
- [x] Medium
- [ ] Low

The failure is intermittent and does not indicate a production defect, but a
flaky test in a protected gate is corrosive: it trains reviewers to re-run
rather than investigate, and it can fail an otherwise-green CI run at random.

## Suspected Cause / Notes

- `QfcItemController.InvokeBeginInvoke` at
`QuickFiler/Controllers/QfcItemController.FocusAndTheme.cs:256` calls
`IItemViewer.Invoke` directly. `Control.Invoke` throws unless the control's
native window handle already exists.
- The tests reach this through `ToggleTips` at
`QuickFiler/Controllers/QfcItemController.FocusAndTheme.cs:204`.
- Handle creation depends on ambient WinForms state in the shared test host,
which differs between a full-suite run and a single-class run. This is exactly
the mutable-global-state dependency the unit-test policy prohibits.
- A sibling test in the same file already documents this hazard and works around
it with a headless `ProgressTrackerPane` built via
`FormatterServices.GetUninitializedObject` (see the comment block in
`UtilitiesCS.Test/Extensions/AsyncSerialization_Tests.cs`), which indicates the
repository already has a pattern for avoiding a live message pump in tests.
- Preferred fix per `.claude/rules/csharp.md` "DI Seams": introduce an interface
or injectable-delegate seam for the invoke path so the test supplies a
synchronous no-op marshaller and never touches a real window handle.
Adding a sleep, a retry, or a handle-forcing call would violate the
"Prohibited Behaviors" section of the same rule.

## Proposed Fix / Validation Ideas

- [ ] Unit coverage areas: `QfcItemController` initialization overloads and
`ToggleTips`, exercised through the new seam rather than a live control.
- [ ] Integration scenario to retest: full nine-assembly suite run repeatedly
(at least 5 consecutive runs) to demonstrate the flakiness is gone.
- [ ] Manual verification notes: confirm the seam's production default still
marshals through the real control so runtime behavior is unchanged.

## Next Step

- [x] Promote to GitHub issue (bug-report template)
- [ ] Move to active fix folder / branch
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# system-reactive-7-packages-config-unsupported (Issue #570)

- Date captured: 2026-08-15
- Author: Dan Moisan
- Status: Promoted -> docs/features/active/system-reactive-7-packages-config-unsupported/ (Issue #570)

> Automation note: Keep the section headings below unchanged; the promotion tooling maps each of them into the GitHub bug issue template.

- Issue: #570
- Issue URL: https://github.com/drmoisan/TaskMaster/issues/570
- Last Updated: 2026-08-15
## Summary

The NuGet upgrade in PR #568 moved System.Reactive to 7.0.0, which explicitly
does not support `packages.config`. Every build of the solution now emits five
warnings from the package's own guard target stating the configuration is
unsupported. The repository's projects are legacy non-SDK `packages.config`
projects, so the package is being consumed in a scenario its authors disclaim.

## Environment

- OS/version: Windows 11 Pro 10.0.26200
- Python version: n/a (C# / .NET Framework 4.8.1)
- Command/flags used: `msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:TreatWarningsAsErrors=true`
- Data source or fixture: `packages/System.Reactive.7.0.0`

## Steps to Reproduce

1. Check out `main` at or after merge commit `97065e55`.
2. Run `nuget restore TaskMaster.sln`.
3. Run the analyzer or nullable toolchain stage, for example
`msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:TreatWarningsAsErrors=true`.
4. Observe the warning summary at the end of the build.

## Expected Behavior

The solution builds with zero warnings from its dependency set, and the reactive
dependency is consumed in a configuration its maintainers support.

## Actual Behavior

The build succeeds with 5 warnings, all of the following form:

```
packages\System.Reactive.7.0.0\build\System.Reactive.PackagesConfigCheck.targets(31,5):
warning : The project contains a packages.config file, which is not supported by
System.Reactive v7.0 or later. Please migrate to PackageReference. (You can
suppress this message by setting the RxUseUnsupportedPackagesConfig property to
true, but be aware this is an unsupported scenario.)
```

Affected projects: `ToDoModel`, `QuickFiler`, `TaskMaster`, `UtilitiesCS.Test`,
and one further project in the same build.

## Logs / Screenshots

- [x] Attached minimal logs or screenshot
- Snippet: see the warning text quoted above; reproduced verbatim from the
local analyzer and nullable toolchain stages on 2026-08-15.

## Impact / Severity

- [ ] Blocker
- [ ] High
- [x] Medium
- [ ] Low

The build is not broken today and both protected gates pass. The severity is
Medium rather than Low because the package guard is not cosmetic: the vendor
states the scenario is unsupported, so assembly-binding or runtime-loading
behavior for Rx may diverge from a supported configuration without further
warning, and the warning count masks any new warning that appears later.

## Suspected Cause / Notes

- The upgrade advanced System.Reactive across a major version boundary (6.x to
7.0.0) where the package added `System.Reactive.PackagesConfigCheck.targets`
as a deliberate guard against `packages.config` consumption.
- The repository is intentionally on `packages.config` (see
`.claude/rules/csharp.md`, "Mechanism"): the projects are legacy non-SDK
VSTO / .NET Framework projects, and PackageReference / Central Package
Management were explicitly not introduced.
- Three responses exist and should be weighed rather than assumed:
1. Pin System.Reactive back to the last 6.x release that supports
`packages.config`.
2. Set `RxUseUnsupportedPackagesConfig=true` to silence the guard, accepting
the vendor-disclaimed configuration.
3. Migrate the affected projects to PackageReference, which conflicts with
the documented repository decision and is a much larger change.
- Option 1 is the most likely correct answer given the repository's documented
stance, but it needs verification that nothing added in Rx 7.0 is required.

## Proposed Fix / Validation Ideas

- [ ] Unit coverage areas: existing reactive-dependent tests in `UtilitiesCS.Test`
and `QuickFiler.Test` must continue to pass under whichever option is taken.
- [ ] Integration scenario to retest: full `vstest.console.exe` run with
`/EnableCodeCoverage` across all nine test assemblies.
- [ ] Manual verification notes: confirm the build warning count returns to zero
and that assembly binding redirects for `System.Reactive` in each
`app.config` still resolve at runtime.

## Next Step

- [x] Promote to GitHub issue (bug-report template)
- [ ] Move to active fix folder / branch
Loading