Skip to content
46 changes: 46 additions & 0 deletions .claude/agent-memory/_shared_no_absolute_host_paths.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Never embed absolute host paths in any file

**Applies to:** every agent, every artifact — evidence records, plans, specs, research,
checkpoints, agent memory, commit messages, PR bodies.

## Rule

No file committed to this repository may contain an absolute host path or a host identifier.
Absolute paths leak the operator's account name, machine name, and directory layout, and they
are not reproducible on any other machine.

Prohibited: `C:\Users\<account>\...`, `C:/Users/<account>/...`, `/c/Users/<account>/...`, a bare
account name (for example in an `ls -l` owner column), and a bare machine name.

## Required placeholders

| Real value | Write this instead |
| --- | --- |
| repository root | `<repo-root>` |
| user profile directory (for caches outside the repo, e.g. NuGet) | `<user-profile>` |
| account name | `<user>` |
| machine / host name | `<host>` |

Compose longer paths from the placeholder: `<repo-root>\.claude\worktrees\agent-<id>\.dotnet-sdk`.
Prefer a repo-relative path (`packages/Foo/bar.dll`) whenever one is expressible — a placeholder is
the fallback for paths that genuinely sit outside the repository.

## The vstest TRX trap

`vstest.console.exe` names its TRX and `.coverage` output `<account>_<HOST>_<timestamp>.trx` by
default, so raw test output embeds both identifiers **in the filename**. Any evidence artifact that
cites a TRX by name inherits them.

Two mitigations, both required:
1. Pass an explicit `/ResultsDirectory:` plus a `--logger:trx;LogFileName=` that you control, or
rename the produced files before citing them.
2. When citing a TRX in a markdown evidence record, cite the sanitized filename.

## Recurrence record

Issue #511 (`winformspumphost-suite-determinism-511`) accumulated 140 untracked evidence paths
carrying a `<account>_<HOST>_` filename prefix, 10 committed markdown files citing those names, and
91 absolute-path occurrences across 27 committed files. All were sanitized on 2026-08-23 at
maintainer instruction. Roughly 146 files in other and archived feature folders still carry the
prefix, and about 157 still carry the bare host name; that remainder is tracked as its own issue
because sanitizing it inside a bug-fix child would break the child's scope lock.
8 changes: 7 additions & 1 deletion .claude/agent-memory/atomic-executor/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
## Build / toolchain environment
- [pwsh/git/gh CLI gotchas](project_pwsh_git_gh_cli_gotchas.md) — jq NOT installed (only `gh --jq`); pwsh won't concatenate `$(git merge-base ...)..HEAD`; bare `packages.config`
- [Project Build/Test Env](project_build_test_env.md) — git-bash quirks (MSBuild switches, MSYS_NO_PATHCONV), csharpier v1 syntax, legacy csproj Compile includes, IVT for Moq
- [Start-Process -ArgumentList array strips quoting](project_startprocess_arglist_array_strips_quoting.md) — a detached msbuild launch loses `"/p:Platform=Any CPU"` and dies MSB1008 having compiled nothing; pass ONE pre-quoted string
- [VS18 build/test toolchain paths](project_vs18_build_toolchain_paths.md) — use VS **18** full-framework msbuild.exe (not .dotnet-sdk, dies on binary resx MSB3822); nuget.exe restore
- [Repo-local SDK install + nullable Rebuild](project_repo_sdk_and_nullable_rebuild.md) — .dotnet-sdk install needs pwsh7; csharpier check/format subcommands; nullable debt scope NOT stable across sessions —
- [vstest TestCaseFilter OR-vs-pipe + fresh-worktree bootstrap](project_vstest_testcasefilter_or_operator_and_env_setup.md) — vstest rejects `OR`, needs `|`; fresh worktree needs restore + global `dotnet-coverage`
Expand All @@ -46,6 +47,7 @@
- [Compile-time red needs body-level refs](project_compile_red_needs_body_level_references.md) — a missing type in a method SIGNATURE suppresses body binding, so an `[expect-fail]` task requiring N named CS0246s

## Test execution & isolation
- [Long runs need a detached process](project_long_runs_need_detached_process.md) — Bash `run_in_background` runners get killed after ~1h, taking `Start-Job` load generators with them; use `Start-Process -PassThru` + foreground `sleep 570` to advance real time
- [Tests must mock GUI; no visible window](feedback_tests_must_mock_gui_no_visible_window.md) — use headless seams (mocked viewers, injected show/focus delegates), never Form.Show/Application.Run
- [#511 is a test-host crash, not N failing tests](project_511_is_a_testhost_crash_not_n_failing_tests.md) — load-driven abort with `Total tests: Unknown` (no readable verdict); `/InIsolation` loop gave 0 failed; never gate on a
- [WinFormsPumpHost tests are load-flaky](project_winformspumphost_tests_load_flaky.md) — QfcItemController_InitializationTests fail with "window handle has been created"/60s timeouts when the box is
Expand Down Expand Up @@ -87,6 +89,7 @@
- [Outlook `Action`/`Exception` ambiguity](project_outlook_action_ambiguity.md) — bare `Action` AND bare `Exception` are CS0104-ambiguous in Outlook-interop files; use

## Component-specific gotchas
- [WebView2 EndInit already creates child handles](project_webview2_endinit_creates_handles.md) — `new ItemViewer()` alone leaves BOTH WebView2 children handle-created, so a `IsHandleCreated == false` assertion is unsatisfiable; also 3 pre-existing UtilitiesCS.Test flakes (shared Console.Out) that break any all-assembly `failed == 0` gate
- [#349 breadcrumb WebView2 gotchas](project_349_breadcrumb_webview2_gotchas.md) — retyped Designer field breaks reflection-injected tests; aggregate async d__ classes for >=90%
- QuickFiler #227 cycle notes: [cycle-4 ToggleFocus](project_qfc227_cycle4_toggle_focus_genuine_test_gotchas.md), [cycle-3 Theme/FolderPredictor seam](project_theme_folderpredictor_seam_retrofit_gotchas.md)
- [ObjectListView TreeListView headless selection](project_objectlistview_treelistview_headless_selection.md) — selection needs a native handle; cache the node via SelectionChanged
Expand All @@ -97,4 +100,7 @@
- [IApplicationGlobals member forces implementers](project_iapplicationglobals_member_forces_implementers.md) — adding a member breaks 7 hand-written test-double stubs beyond scope lock; Moq mocks auto-implement
- [TimeProvider seam gotchas](project_timeprovider_seam_gotchas.md) — Moq can't mock non-virtual GetLocalNow (use FakeTimeProvider); an optional TimeProvider param forces a Bcl.TimeProvider
- [ScoDictionaryNew needs TryAdd not Add](project_scodictionarynew_tryadd_not_add.md) — retargeting Sco* tests: `.Add(k,v)` won't compile (CS1061); the base exposes `.TryAdd`; swap in the same edit
- [FluentAssertions Equal(params) has no because](project_fluentassertions_equal_params_no_because.md) — a trailing reason on `.Equal(...)` becomes an extra expected element; use `.Equal(new[]{...})` or move the reason to
- [FluentAssertions Equal(params) has no because](project_fluentassertions_equal_params_no_because.md) — a trailing reason on `.Equal(...)` becomes an extra expected element; use `.Equal(new[]{...})` or move the reason to `.HaveCount(n, reason)`

## Artifact hygiene
- [Never embed absolute host paths](../_shared_no_absolute_host_paths.md) — no `C:\Users\<account>\...`, bare account, or machine name in ANY artifact; use `<repo-root>` / `<user-profile>` / `<user>` / `<host>`. vstest names TRX `<account>_<HOST>_<ts>.trx` by default, so control `/ResultsDirectory:` + `LogFileName=` or rename before citing.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ TaskMaster is a .NET Framework 4.8 C#/VSTO solution. Running the repo toolchain
- QuickFiler.Test compiles as C# 7.3: `using var` and other C# 8+ features fail (CS8370). Use classic `using (...) {}` blocks in that project. TaskVisualization.Test is ALSO C# 7.3 — `is not null` / `is not` patterns fail there with CS8370 ("Feature 'not pattern' is not available in C# 7.3"); use `!= null`. Production projects (e.g. TaskVisualization.csproj) allow the newer patterns, so mirroring a production `is not null` into a `.Test` file breaks the analyzer build. This surfaces at the analyzer/type-check msbuild step, not at edit time.
- COROLLARY (nullable gate order dependency): QuickFiler.Test.csproj sets NO `<LangVersion>` (defaults to C# 7.3 for v4.8.1). The mandated nullable command `-p:Nullable=enable -p:TreatWarningsAsErrors=true` is solution-wide, so if QuickFiler.Test actually recompiles under it, it emits `CS8630: Invalid 'nullable' value 'Enable' for C# 7.3` and the build exits 1. This surfaces ONLY when you run the nullable build IN ISOLATION right after editing QuickFiler.Test sources. The fix is simply the mandated toolchain ORDER: run the analyzer build (`-p:EnableNETAnalyzers=true -p:EnforceCodeStyleInBuild=true`, which does NOT set Nullable) FIRST to compile QuickFiler.Test under its real 7.3 settings, then the nullable build finds it up-to-date and skips it, so CS8630 never arises. Do not "fix" CS8630 by editing the csproj — it is an isolation artifact, not a defect.
- `dotnet-coverage` global tool (v18.5.2) converts `.coverage` -> Cobertura cleanly: `MSYS_NO_PATHCONV=1 dotnet-coverage merge -o out.cobertura.xml -f cobertura <file>.coverage`. Root element is `<coverage line-rate=".." lines-covered=".." lines-valid="..">` and `<class filename="<full win path>">` blocks carry `<line number="N" hits="H" .../>`; `[ExcludeFromCodeCoverage]` classes (e.g. `QfcDatamodel`) are absent entirely. This is a working alternative to Microsoft.CodeCoverage.Console.exe.
- A FRESH worktree has no `packages/` dir. The first msbuild fails with CS0246 (`Fizzler`/`Svg`/`log4net` not found) in vendored SVGControl. Run `nuget restore TaskMaster.sln` first (packages.config-based, ~168 packages). nuget.exe is at `C:/Users/DanMoisan/AppData/Local/Microsoft/WinGet/Packages/Microsoft.NuGet_Microsoft.Winget.Source_8wekyb3d8bbwe/nuget.exe`.
- A FRESH worktree has no `packages/` dir. The first msbuild fails with CS0246 (`Fizzler`/`Svg`/`log4net` not found) in vendored SVGControl. Run `nuget restore TaskMaster.sln` first (packages.config-based, ~168 packages). nuget.exe is at `<user-profile>/AppData/Local/Microsoft/WinGet/Packages/Microsoft.NuGet_Microsoft.Winget.Source_8wekyb3d8bbwe/nuget.exe`.
- Standalone project builds (`msbuild Foo.csproj`) fail with "BaseOutputPath/OutputPath is not set" when `-p:Platform="Any CPU"` (with space) is passed, because the solution maps the per-project platform. Either build via `TaskMaster.sln` (which has the `Any CPU` solution config) or pass `-p:Platform=AnyCPU` (no space) for the standalone project. The forced-nullable Rebuild of UtilitiesCS uses `-p:Platform=AnyCPU`.
- Full UtilitiesCS.Test run (~3814 tests) has ONE pre-existing flaky failure: `AddEntry_UseUiThreadTrue_DequeuesEntryAndSuppressesDispatcherException` (UI-thread/dispatcher timing). It fails at baseline independent of any change; capture the baseline failure set so post-change runs can be compared as "same single pre-existing failure" rather than a new regression.
- Coverage convert: latest `.coverage` is under `TestResults/<guid>/*.coverage`; convert with `Microsoft.CodeCoverage.Console.exe merge <file> -f xml -o out.xml`. Tool is at `C:/Program Files/Microsoft Visual Studio/18/Community/Common7/IDE/Extensions/Microsoft/CodeCoverage.Console/Microsoft.CodeCoverage.Console.exe`. The merged XML instruments ALL loaded modules (vendored/third-party too), so whole-process line coverage looks low (~54%); the policy 80% gate applies to first-party modules, e.g. `UtilitiesCS.dll` ~87%. Per-method coverage: async methods appear as `<MethodName>d__N.MoveNext` functions with `type_name` set, so resolve the method by `type_name` containing `<MethodName>`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
name: long-runs-need-detached-process
description: Bash-tool background tasks get killed on long runs, taking Start-Job load generators with them; launch multi-hour runners with Start-Process -PassThru instead
metadata:
type: project
---

A multi-hour runner launched via the Bash tool's `run_in_background` was **killed mid-run** by the
session's background-task lifecycle after roughly an hour, discarding three completed suite runs.
Relaunching the identical script through a detached `Start-Process` survived the full 2.5-hour
window.

**Why:** `run_in_background` tasks are owned by the session and can be stopped. PowerShell
`Start-Job` workers are children of the runner process, so when the runner dies the load generator
dies with it — which is at least fail-safe (no orphan busy loops), but the window is lost and must
be restarted from scratch, because a load window's start/stop utilization samples must bracket a
single continuous run.

**How to apply:** For anything over ~30 minutes, launch it detached and record the PID:

```powershell
$p = Start-Process -FilePath 'pwsh' `
-ArgumentList @('-NoProfile','-NonInteractive','-File', $script) `
-RedirectStandardOutput $log -RedirectStandardError $err `
-WindowStyle Hidden -PassThru
Set-Content -LiteralPath $pidFile -Value $p.Id
```

Then poll the log file. To make real wall-clock time pass, issue a FOREGROUND
`sleep 570` with `timeout: 600000`; it runs the full ~9.5 minutes before being moved to background.
Repeated `run_in_background` sleeps do NOT advance time reliably, because each completion notifies
you immediately and you resume within seconds.

Have the runner append one line per iteration to its log and rewrite a rows JSON after each
iteration, so a kill loses at most the in-flight iteration and the completed ones stay auditable.

Also verify after any kill: `Get-Process pwsh | Select Id,CPU,StartTime` and confirm no worker from
your start time survives. Do not kill processes whose `StartTime` predates your session — see
[[project-sibling-worktree-shared-tooling-hazard]].
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ execute it once before trusting it. `-File` invocations are unaffected (no shell
`$` in the payload). Related: [[project_build_test_env]],
[[project_poshqc_pester_mcp_exit_minus1]].

**Backtick corollary (measured 2026-08-23):** inside a *double-quoted* PowerShell string the
backtick is the escape character, so a byte-exact string replacement whose literal contains a
Markdown or C# backtick silently finds **0 matches** rather than erroring. Replacing
``- [ ] `BuildPumpHarness_...` `` in a spec file reported `match=0` until the literal was moved
into a single-quoted string. Any exact-block replace against Markdown or C# must use
single-quoted PowerShell strings (doubling `''` for apostrophes), and must assert the
match count is exactly 1 before writing.

Corollary measured at the same time: Pester 5.6.1 creates `CodeCoverage.OutputPath`'s
parent directory (`New-Item -Force -ItemType Container`), so redirecting coverage into a
not-yet-existing evidence folder is safe. Pester also ignores `Run.Exit` by default, so a
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
name: startprocess-arglist-array-strips-quoting
description: Detaching msbuild via `Start-Process -ArgumentList @(...)` silently drops the quoting on `/p:Platform=Any CPU`, so the gate dies with MSB1008 having compiled nothing; pass ONE pre-quoted argument string instead.
metadata:
type: project
---

When launching `MSBuild.exe` (or `vstest.console.exe`) detached via
`Start-Process -PassThru -RedirectStandardOutput ...`, pass the arguments as a **single
pre-quoted string**, not as an array:

```powershell
# WRONG - dies with MSB1008, compiles nothing, exit code 1
$argList = @('TaskMaster.sln','/t:Rebuild','/m','/p:Configuration=Debug',
'/p:Platform=Any CPU','/p:EnableNETAnalyzers=true')

# RIGHT
$argList = 'TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:EnableNETAnalyzers=true'
```

**Why:** `Start-Process` joins the array elements with spaces to build the child's command
line and does NOT re-quote an element that contains a space. `/p:Platform=Any CPU` arrives
as two arguments, so MSBuild sees a second "project" and exits 1 with:

```
MSBUILD : error MSB1008: Only one project can be specified.
Full command line: '"...MSBuild.exe" TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug /p:Platform=Any CPU ...'
```

Measured 2026-08-23 on the #511 remediation cycle. The failure is quiet in the worst way: the
launcher itself succeeds, `$p.ExitCode` is a legitimate 1, and the log's own
`Full command line:` echo is the only place the missing quotes are visible. A gate that only
checked "did the process exit" would have recorded a red analyzer gate as a code defect.

**How to apply:** this is the exact same class of defect as
[[bash-tool-mangles-msbuild-switches]] one layer further in — the Bash tool mangles `/m` into
`M:/`, and `Start-Process -ArgumentList @(...)` mangles `"/p:Platform=Any CPU"`. Whenever a
plan mandates a detached long-run mechanic (`Start-Process -PassThru` + poll + take
`ExitCode` from the process object), build the argument line as one string and grep the log
for `Full command line:` on the first launch to confirm the quotes survived. Related:
[[project_pwsh_command_quoting_from_bash]], [[project_long_runs_need_detached_process]],
[[project_build_test_env]].
Loading
Loading