feat(tools): share the native-command guard, and report which Copilot reviews are outstanding - #81
Conversation
… reviews are outstanding
Two pieces of tooling, peeled off the long-running deferred-namespace-ops
branch so they can be reviewed on their own. Neither touches any crate.
## The native-command guard
Under Windows PowerShell 5.1 a native command that writes to stderr while
`$ErrorActionPreference` is `Stop` raises a TERMINATING error when its stderr
is redirected with `2>&1`. PowerShell 7 does not, which is why this class
survives review and CI.
`run-numa-spikes.ps1` had two such captures and no guard, and the shape of
that failure is what makes it dangerous. `cargo --quiet` writes nothing to
stderr on a clean build, so under 5.1 the script ran to completion for as long
as every spike was healthy. It threw only when cargo did write there -- a
warning, or a failed compile -- which is exactly the case the script exists to
report. The throw landed before `$buildExit` was assigned, so the
broken-instrument branch never ran: no transcript, no summary, and the one
artifact somebody downloads to diagnose a rotted spike was the one case that
never produced it. Measured both ways against a deliberately uncompilable
crate: unguarded, 5.1 threw and captured nothing; guarded, exit 101 with all
seven diagnostic lines.
The guard already existed as three hand-copied copies, so this shares it --
and HOW it is shared is a correctness requirement, not a style choice. A
scriptblock carries the session state it was created in, so
`Invoke-Native { cargo build }` runs in the CALLER's scope while a `.psm1`
copy flips the preference in the MODULE's scope; the flip never reaches the
call. Measured with the identical body in a module: under 5.1 seven of eight
cases failed while all eight passed under PowerShell 7. Dot-sourcing puts the
function in the caller's own scope, where the plain assignment does reach it.
A module can be made to work through `$PSCmdlet.SessionState.PSVariable.Set`,
measured working on both hosts, and is rejected: the guard would rest on a
subtlety that looks removable, and simplifying it back reintroduces a defect
that still passes on PowerShell 7 and in CI.
`test-common.ps1` covers it on BOTH hosts -- it runs its cases in the invoking
host, re-invokes itself in the other, and treats a missing host as a failure
rather than a skip. The sabotage job now runs under `pwsh` and `powershell`
for the same reason: it ran pwsh-only, which is precisely why this was
invisible.
`run-mutants.ps1` was checked for the same defect and needs no change -- it
redirects nothing, confirmed by experiment on both hosts.
## The review scanner
PR #56 has accumulated 197 Copilot reviews and nothing said which had been
dealt with. Findings arrive in two shapes and only one has state: inline
comments become threads that can be RESOLVED, while suppressed comments exist
only as prose in the review body and create no thread. A review is not a
reactable object either -- `POST /pulls/{n}/reviews/{id}/reactions` returns
404 while the same call on an inline comment succeeds -- so nothing anywhere
records that a suppressed finding was read.
`scan-pr-reviews.ps1` reports both, and `-MarkProcessed` closes the gap with a
marker comment on the pull request:
<!-- copilot-review-processed: 5136043258 -->
An HTML comment, so it does not render; on the pull request rather than in a
file or a session, so it survives a new machine, contributor, or agent
session; read back by the script, so a handled review stops being reported. A
`-Summary` is required alongside it, because a marker with no account of what
was done is a claim with no evidence.
Exercised end to end on PR #80: six threads resolved, one suppressed-only
review marked, re-scan clean at exit 0.
## Deliberately not included
The `Write-Report` functions are NOT consolidated. Six scripts define one and
they are not duplicates -- they differ in level vocabulary and in rendering,
with two emitting GitHub Actions annotations and four emitting console
colours. Merging them would change six tools' output to remove a duplication
that is only apparent.
The M34.4 checklist bookkeeping is not here either: `main` has no M34
milestone, which lives in the source branch's documentation cluster, so the
stub and its archive entry travel with that stage rather than being
half-landed here.
Verified on both hosts: `test-common.ps1` passes, the sabotage suite passes
under 7 and 5.1, `run-numa-spikes.ps1` completes, and the encoding and
workflow-reference checks pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟢 Approval recommended
The changes are self-contained to tooling/CI, add cross-host test coverage for the 5.1-specific failure mode, and do not introduce correctness or safety issues in the reviewed diffs.
Pull request overview
This PR improves the reliability and maintainability of the repository’s PowerShell tooling by centralizing the Windows PowerShell 5.1 native-stderr capture guard (via dot-sourced shared support) and adding a script that reports which GitHub Copilot review findings remain outstanding, including suppressed-only reviews that otherwise have no durable “handled” state.
Changes:
- Introduce
tools/common.ps1as the shared, dot-sourced home forInvoke-Native/ConvertTo-OutputLines, and update multiple tools to use it for native command capture under Windows PowerShell 5.1. - Add
tools/test-common.ps1and wire CI to validate the guard across both PowerShell hosts (pwsh + Windows PowerShell 5.1), plus run the sabotage harness tests under both shells. - Add
tools/scan-pr-reviews.ps1and corresponding design notes to track unresolved Copilot review threads and to mark suppressed-only review bodies as processed via PR comments.
File summaries
| File | Description |
|---|---|
| tools/common.ps1 | New shared, dot-sourced helper implementing the 5.1 native stderr capture guard and output normalization. |
| tools/test-common.ps1 | New cross-host test suite validating Invoke-Native behavior and refusing to pass if the other host is missing. |
| tools/test-run-sabotage.ps1 | Dot-sources common.ps1 and routes native git/shell capture through Invoke-Native to avoid 5.1 terminating errors. |
| tools/soak-flush-barrier.ps1 | Removes in-file guard copies and dot-sources common.ps1 to eliminate drift across capture sites. |
| tools/run-sabotage.ps1 | Dot-sources common.ps1 and uses Invoke-Native for git calls that redirect stderr under Stop. |
| tools/run-numa-spikes.ps1 | Dot-sources common.ps1 and uses Invoke-Native for cargo build/run captures to behave consistently on 5.1. |
| tools/scan-pr-reviews.ps1 | New tool that scans Copilot reviews/threads and supports durable “processed” markers for suppressed-only findings. |
| DESIGN-NOTES.md | Records the dot-sourcing requirement for shared tools support and the Copilot review ledger convention. |
| .github/workflows/ci.yml | Adds test-common.ps1 and runs the sabotage harness tests under both pwsh and Windows PowerShell 5.1. |
Review details
- Files reviewed: 9/9 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…tests able to fail Code review found that `Invoke-Native`'s `try/finally` restoration was dead code and that three cases in `test-common.ps1` claimed to cover it while being unable to fail. Both hold, and verifying it turned up a second defect the review did not reach. **The restoration was a no-op.** `$ErrorActionPreference = 'Continue'` assigns to a FUNCTION-LOCAL variable -- PowerShell assignment always writes to the current scope -- so the caller's value was never modified and the local one is discarded on return. `& $Command` still inherits `Continue` because the scriptblock runs in a child of that scope, which is the reach the guard needs. Measured on both hosts: a variant with the `try/finally` deleted outright leaves the caller reading `Stop` immediately after the call, identically to the version that had it. Removed, with the scoping rule stated where the assignment is. **The three cases were aimed at the wrong property.** They are not worthless, which is worth being precise about: a mutant that writes `$script:ErrorActionPreference` instead leaves the CALLER running under `Continue` for everything afterwards, and that is the mutation actually worth guarding. They are now described as testing that the flip does not ESCAPE, which is what they establish. **The second defect: they could be defeated by their own contamination.** Each captured `$before = $ErrorActionPreference` rather than establishing a known value. Under a `$script:`-scoped mutant the leak happens on the FIRST call, so a later case captured `Continue`, compared it against `Continue` afterwards, and passed -- the contamination hiding itself. Measured before the fix: that mutant was caught by only ONE of the three, the behavioural case that observes `Stop` through a failing cmdlet rather than through the variable. Each case now sets `$script:ErrorActionPreference = 'Stop'` first, and all three catch it on both hosts. **And a real gap the review's finding exposed: PowerShell 7 had no coverage at all that the guard did anything.** Removing the `Continue` flip fails loudly on 5.1, but 7 captures stderr either way, so every case passed there against a guard that did nothing. Added `the flip reaches the scriptblock it is handed`, which observes the preference from inside the passed scriptblock and is therefore host-independent. It is the only case that fails on 7 against a removed flip. Sabotage-verified, both mutants on both hosts: escape to $script: 7 : 3 cases fail (was 1 before the isolation fix) escape to $script: 5.1 : 3 cases fail flip removed 7 : 1 case fails (was 0 before the new case) flip removed 5.1 : 8 cases fail Nine cases now, passing on both hosts. The sabotage suite passes on both, the NUMA spike runner completes, and the encoding check is clean. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
The new documentation in tools/common.ps1 and DESIGN-NOTES.md contradicts the intended "no restoration is needed/attempted" behavior and should be corrected to match the implementation.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 9/9 changed files
- Comments generated: 2
- Review effort level: Lite
…st markers from write access Three defects from a review round on this branch. Two were the reviewer's; the third was found by running the tool this branch adds against its own pull request, which reported it. **scan-pr-reviews.ps1 exited 1 for both "there are findings" and "the tool broke".** Every error path was an unhandled `throw`, and an unhandled throw from a script also exits 1, so a caller gating on the code could not tell "2 unresolved threads" from "gh is not authenticated" or "no such pull request". That is the instrument-versus-finding distinction `run-numa-spikes` and `run-sabotage` already make by exit code. Error paths now leave through `Exit-Broken` with code 2; 1 stays the finding. A `ConvertFrom-Json` failure is covered too, which is the realistic shape of a proxy returning an HTML error page to a `gh` that still exited 0. Verified on both hosts: nonexistent PR -> 2, missing -Summary -> 2, PR with open threads -> 1, clean PR -> 0. **Processed markers were honoured from any comment author.** This repository is public, so anyone able to comment could post `<!-- copilot-review-processed: N -->` and permanently retire a review from every future scan. Because a suppressed-only review has no state anywhere else -- the whole reason the tool exists -- that would silently delete the only record that a finding was never read, and the summary would just report a smaller number. Only OWNER, MEMBER and COLLABORATOR are now honoured; CONTRIBUTOR means merely "has had a pull request merged" and is not enough. `author_association` arrives on every comment in the same request, so this costs no extra call. Markers from other authors are counted and reported rather than dropped in silence, because such a marker is either an honest mistake or an attempt to retire a finding and both are worth seeing. Verified on both hosts across all six association values, including a missing field, which fails closed; the real OWNER marker on PR #80 is still honoured, so the legitimate path is unaffected. **Restatement drift: two places still said the guard "restores" the preference.** The previous commit removed that restoration as dead code and corrected the implementation comment, but left the claim standing in `common.ps1`'s own summary paragraph and in DESIGN-NOTES. Both now state what the code does -- the flip is function-local rather than restored -- and why the property worth testing is that it does not escape. Swept `restor` across `tools/` and DESIGN-NOTES: 49 matches, 2 stale and corrected, the rest about file restoration in the sabotage harness and unrelated crate behaviour. Found by running `scan-pr-reviews.ps1 -Pr 81`, which is the tool this branch adds; it reported both stale sites as unresolved Copilot threads. The instrument catching a defect in its own change set is the outcome it was written for. Verified: `test-common.ps1` passes on both hosts, the sabotage suite passes on both, encoding 615 files clean, workflow references resolve. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
scan-pr-reviews.ps1 should explicitly fail fast when gh is missing to avoid misclassifying a broken instrument as a JSON/parse error.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 9/9 changed files
- Comments generated: 1
- Review effort level: Lite
…e all-clear
Copilot raised that `scan-pr-reviews.ps1` should preflight `gh`. Measuring it
found the consequence is worse than either the report or my own expectation.
The report's stated mechanism was a stale `$LASTEXITCODE` surfacing as a
misleading JSON-parse failure. That is not what happens. `Invoke-Native { gh
... }` with `gh` absent throws CommandNotFoundException on both hosts, and the
script then died on something else entirely: `$LASTEXITCODE` is UNSET until a
native command has run in the session, and under `Set-StrictMode -Version
Latest` reading an unset variable is itself a terminating error. So the failure
was a StrictMode violation about `$LASTEXITCODE`, describing the wreckage
rather than the missing tool.
And the exit code was the real damage. Measured before and after, both hosts,
with `gh` stripped from PATH:
pre-fix exit 0 <- "Nothing outstanding."
post-fix exit 2 <- broken instrument
Exit 0 is this tool's "nothing outstanding" signal, so a machine without the
GitHub CLI got a silent false all-clear from the one tool whose entire purpose
is to stop findings being lost. That is strictly worse than the exit-1
collision the previous commit fixed for the other error paths, and it is the
same class: a broken instrument reporting as a result.
Two changes, because the preflight alone would leave the landmine for any other
path that reads the variable first:
- `gh` is checked once, up front, and its absence exits through `Exit-Broken`
with a message naming the cause and the fix.
- `$LASTEXITCODE` is read through `Get-LastExitCode`, which uses `Get-Variable
-ErrorAction SilentlyContinue` and treats unset as failure rather than
throwing. Both call sites now distinguish "unset" from "non-zero" and report
the code they saw.
Verified on both hosts: without `gh`, exit 2 with the intended message; with
`gh`, the contract still holds -- findings 1, clean 0, nonexistent PR 2.
`test-common.ps1` and the sabotage suite pass on both hosts; encoding clean.
A note on measurement, since it bit twice while checking this: `pwsh -Command
"& script.ps1"` does NOT propagate the script's exit code, and `Select-Object
-First N` stops the pipeline in a way that makes `$LASTEXITCODE` unreliable.
Both produced wrong readings that looked plausible. The numbers above come from
`-Command "...; exit $LASTEXITCODE"` with no truncation.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟢 Approval recommended
The changes are confined to tools/CI/docs, add cross-host coverage for the 5.1-specific failure mode, and don’t alter crate code or public APIs.
Review details
- Files reviewed: 9/9 changed files
- Comments generated: 0 new
- Review effort level: Lite
…lds exiting as findings Two findings from a review round, both confirmed by measurement on both hosts. **Nullable API fields would have exited 1 -- the "findings" code.** Several property chains over GraphQL and REST data are nullable BY SCHEMA rather than by accident: `author` and `pullRequestReview` are null for a deleted account, REST `user` likewise, `submitted_at` is null on a PENDING review, and a thread's `comments.nodes` can be empty. Under `Set-StrictMode -Version Latest` every one of those is a TERMINATING error, and an unhandled throw from a script exits 1 -- which the exit contract added two commits ago defines as "there are outstanding findings". A contributor deleting their GitHub account would have silently converted this tool into a false positive. Measured on both hosts: `$null.login`, `@()[0]` and `[datetime]$null` all throw under StrictMode, and a JSON-sourced null behaves identically. Added `Get-Path`, which walks a property chain yielding `$null` instead of throwing, and routed every such access through it; a thread that cannot be attributed to a review is skipped rather than attributed to review 0, and a PENDING review renders as `pending` rather than being cast. **The marker-trust check was wider than it claimed.** It trusted `author_association` in OWNER/MEMBER/COLLABORATOR and described those as "the ones that imply write access". They are not: GitHub reports `COLLABORATOR` for anyone invited to collaborate, with no permission qualifier, so a collaborator with `read` or `triage` could post a marker and permanently retire a suppressed-only finding that has no other state anywhere -- without even being counted as ignored. Replaced with an actual permission lookup against the collaborators endpoint, accepting only `admin` or `write`. One call per DISTINCT marker author, cached, and markers are rare, so this is a call or two per scan. It **fails closed**: a 404 for a non-collaborator, a 403 because the account running the scan cannot query permissions, or any network failure leaves the marker unhonoured. That direction is deliberate -- over-reporting a handled finding is visible and recoverable, while wrongly honouring a marker silently deletes the only record that a finding was never read. The lookup deliberately does not go through `Invoke-GitHubJson`, because a non-zero exit there is the ordinary answer for a non-collaborator rather than a broken instrument. Verified against real accounts on both hosts: the owner may retire, `octocat` and `torvalds` may not, an empty login may not; and the existing OWNER marker on PR #80 is still honoured, so the legitimate path is unchanged. The claim is corrected in both places that stated it -- the script header and DESIGN-NOTES -- since the old wording described an enforcement the code did not perform. Swept for the stale phrasing; no occurrences remain. Verified: `test-common.ps1` passes on both hosts, the sabotage suite passes on both, exit contract intact (findings 1, clean 0, nonexistent PR 2), encoding 615 files clean, workflow references resolve. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟢 Approval recommended
The changes consistently centralize the measured 5.1-safe native-capture guard, add cross-host validation in both tests and CI, and introduce the review scanner without introducing observable correctness issues in the updated scripts.
Review details
- Files reviewed: 9/9 changed files
- Comments generated: 0 new
- Review effort level: Lite
… its author
A review round found that the marker-trust check collapsed two different
outcomes into one message, and the message asserted the one the run had not
established. That is the same defect this whole tool exists to prevent, so it
is worth naming precisely.
`GET /repos/{owner}/{repo}/collaborators/{user}/permission` requires the CALLER
to have push access. An account without it gets a flat 403 for every login it
asks about -- including the maintainer who wrote the markers. The old code
mapped every non-zero exit to `$false` and printed:
ignored markers: N (author lacks write access)
In that case the author has `admin`. A reader following the message goes and
checks the marker author's collaborator role, which is fine, rather than their
own token, which is the actual cause.
Measured against the live API, there are three outcomes and the old code had
two. The third is not an error at all, which is what made the conflation easy:
exit 0, admin|write -> allowed
exit 0, read|none -> denied (a statement about the author)
exit 1, HTTP 403 -> unverifiable (a statement about the caller)
`octocat` on this repository returns exit 0 with `read`, and
`github-actions[bot]` returns exit 0 with `none` -- both successful answers, not
failures. Only the 403 is a failure, and only it is unverifiable.
`Get-RetireAuthority` now returns those three, counted and reported separately.
The unverifiable message says whose problem it is and that every marked review
is being re-reported as a consequence. All three still fail closed -- only
`allowed` honours a marker -- because over-reporting a handled finding is
visible and recoverable, while wrongly honouring one silently deletes the only
record that a finding was never read.
The bot result is worth its own note, and is now in the script header and
DESIGN-NOTES: a marker posted from a workflow using GITHUB_TOKEN is written by
`github-actions[bot]`, whose permission reads `none`, so it can never be
honoured. Mark from a real account. Measured rather than assumed.
Verified on both hosts against real accounts -- owner allowed, non-collaborator
denied, bot denied, a repository where this account lacks push unverifiable, and
a comment with no author denied -- 5 of 5 correct. Exit contract intact
(PR #80 0, PR #81 0, nonexistent PR 2), `test-common.ps1` and the sabotage suite
pass on both hosts, encoding 615 files clean.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟢 Approval recommended
The changes are cohesive, well-documented, and the added cross-host CI coverage directly validates the correctness-critical PowerShell 5.1 behavior the refactor is intended to enforce.
Review details
- Files reviewed: 9/9 changed files
- Comments generated: 0 new
- Review effort level: Lite
… guard A review round found a native capture this branch had missed. The miss was a sweep failure on my part, and the correction is a wider sweep rather than one line: I had grepped for `2>&1` and converted those, but `2>$null` is the same hazard and I never looked for it. Measured, both spellings, Windows PowerShell 5.1 under `Stop`: git rev-parse ... 2>$null THREW RemoteException git rev-parse ... 2>&1 THREW RemoteException git rev-parse ... OK, LASTEXITCODE=128 **`run-sabotage.ps1`'s `Get-RepoRoot` was the live one, and it inverted this script's own exit contract.** Its whole failure mode IS stderr -- outside a working tree git says `fatal: not a git repository` -- so on 5.1 the deliberate `Exit-WithMessage ... 2` line beneath it was unreachable. The harness died with NativeCommandError and exited 1, which in that script means "sabotages did not behave as declared": a broken instrument reported as a finding, which is the exact separation this branch exists to make. PowerShell 7 reached the intended path either way, which is why it looked fine. Nothing tested it, because the suite only ever ran the harness INSIDE a fixture repository. Added a case that runs it from a non-git directory and asserts exit 2, and sabotage-verified it by reverting the guard: guard reverted, PS 7 -> PASS (the bug is invisible here) guard reverted, PS 5.1 -> FAIL That is the host-specific signal this branch's new `shell: powershell` CI step exists to surface. **`check-workflow-refs.ps1`'s `cargo metadata ... 2>$null` was latent**, and is converted too. Cargo writes to stderr routinely; it happens to stay silent on a warm workspace, which is what kept this hidden. A cold one printing "Downloading", or any warning, would have killed the check on 5.1 while passing on 7. That script does run on 5.1, so the fix is live there. **`check-encoding.ps1` carries the same shape and is deliberately NOT changed.** Converting it looked right and would have been theatre: measured on both this branch and origin/main, the file does not parse under Windows PowerShell 5.1 at all. It has no BOM and contains UTF-8 mojibake fixture strings, which 5.1 reads as the ANSI code page, so it fails at parse time long before reaching any git call. The hazard cannot manifest there. That is a real pre-existing defect -- a repository check that cannot run on one of the two hosts -- but it is a different one, in a file this branch does not otherwise touch, and fixing it means changing that file's encoding rather than its git call. Verified: the new case passes on both hosts and fails on 5.1 against the reverted guard; `test-common.ps1` and the full sabotage suite pass on both; `check-workflow-refs.ps1` passes on both; encoding 615 files clean; the review scanner self-reports 0 on this PR. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🔵 Needs a closer look
The new review-marker mechanism in tools/scan-pr-reviews.ps1 can unintentionally honor quoted/example markers in normal PR comments, which can incorrectly suppress outstanding reviews unless marker parsing is tightened.
Review details
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
tools/scan-pr-reviews.ps1:186
- Marker parsing currently honors any
<!-- copilot-review-processed: ... -->anywhere in a PR comment body, which can accidentally retire a review if someone with write access quotes a marker as an example. Consider bracketing tool-emitted markers with explicit begin/end sentinels so only intentional marker blocks are recognized.
This issue also appears on line 318 of the same file.
tools/scan-pr-reviews.ps1:319
- The scan treats any marker substring in an issue comment as authoritative. If a maintainer quotes a marker in normal discussion, it will be honored and suppress the review. After adding begin/end sentinels in the emitted marker comment, restrict recognition to markers inside that sentinel block (and ideally anchored to whole lines) to avoid accidental matches.
$markers = [regex]::Matches((Get-Text $c.body), '<!--\s*copilot-review-processed:\s*(\d+)\s*-->')
if ($markers.Count -eq 0) { continue }
- Files reviewed: 10/10 changed files
- Comments generated: 0 new
- Review effort level: Lite
…s parsed output
The previous commit routed `cargo metadata` through `Invoke-Native` and turned
CI red while passing locally. The cause is a semantic difference between the two
redirects that I had treated as equivalent, and it is worth stating plainly
because the whole point of that commit was that any stderr redirect is a hazard
on 5.1:
2>$null DISCARDS stderr
Invoke-Native MERGES stderr into the capture (2>&1)
For a transcript, merging is what you want. For output that is PARSED, it is
corruption. `cargo metadata`'s stdout is JSON, and a cold CI runner emits
rustup's `info: syncing channel updates` on stderr, so the capture became
`info: ... {json}` and `ConvertFrom-Json` failed with "Unexpected character
encountered while parsing value: i". A warm workspace writes nothing to stderr,
which is exactly why it passed here and failed there.
Reproduced on both hosts against a stand-in that writes both streams, with the
identical error message, and confirmed the fix on both.
Added `Invoke-NativeStdout`, which keeps the `Continue` flip -- `2>$null` is
still a redirect and still terminating on 5.1 under `Stop` -- while discarding
stderr instead of merging it. Swept every `Invoke-Native` call site and
converted the four whose output is parsed rather than shown:
check-workflow-refs.ps1 cargo metadata -> JSON
run-sabotage.ps1 git rev-parse -> a path
scan-pr-reviews.ps1 gh api -> JSON
scan-pr-reviews.ps1 gh api --jq -> a permission string
The last two matter beyond tidiness: a notice on `gh`'s stderr would have
corrupted every API response, and in the permission case would have made a
genuine writer read as denied -- the marker-trust control failing in the
direction that looks like a legitimate refusal. The remaining call sites are
transcripts or piped to Out-Null, where merging is intended.
Four cases added to test-common.ps1, including the reduced form of the exact CI
failure: stderr discarded rather than merged, JSON parseable under stderr noise,
the exit code preserved, and no throw on 5.1 despite the redirect. Thirteen
cases now, passing on both hosts.
Verified: check-workflow-refs, the sabotage suite and the review scanner all
pass on both hosts; encoding 615 files clean.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
scan-pr-reviews.ps1 currently discards gh stderr even on failure, making broken-instrument errors significantly harder to diagnose.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 10/10 changed files
- Comments generated: 1
- Review effort level: Lite
…r the text appears Copilot review 5137260817 on PR #81, whose two findings were both suppressed -- so they created no thread and, without this branch's own scanner, nothing would have surfaced them at all. Marker recognition matched `<!-- copilot-review-processed: N -->` ANYWHERE in a comment body. That cannot tell a comment that IS a marker from one that merely MENTIONS one, so a maintainer quoting a marker in discussion -- or pasting the example out of this script's own documentation -- would silently retire a review. Since a suppressed-only review has no other state anywhere, retiring it deletes the only record that a finding was never read. Not currently firing: the sole marker in the repository is the legitimate one on PR #80. But this branch publishes the literal marker text in its own PR body and DESIGN-NOTES, so the material for an accidental retirement is already written. Markers are now emitted bracketed: <!-- copilot-review-processed:begin --> <!-- copilot-review-processed: N --> <!-- copilot-review-processed:end --> and honoured only on a line of their own inside such a block. The sentinels are defined once so emitter and reader cannot drift, which is the failure this whole tool is about. Both documentation examples now spell the id as a placeholder rather than digits, so copying an example verbatim -- sentinels included -- matches nothing. Verified 9 discrimination cases on both hosts, 9 of 9: a real marker and a two-id block are honoured; a bare marker, one quoted inline in prose, one inside a fenced block, the placeholder example, a marker sharing a line with a sentinel, an unterminated block, and an empty body are all rejected. The existing marker on PR #80 was bare, so it correctly stopped counting -- the scan went back to exit 1 there, which is the fail-closed direction. Re-marked in the new format and confirmed exit 0 again; its finding is unchanged. Verified: exit contract intact on both hosts (PR #80 0, nonexistent PR 2), test-common.ps1 13 cases pass on both, the sabotage suite passes on both, encoding 615 files clean, workflow references resolve. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Both suppressed findings addressed in 4b0134d. They were the same finding at two sites: marker recognition matched the marker text anywhere Markers are now emitted inside Verified 9 discrimination cases on both PowerShell hosts, 9 of 9: real marker and a two-id block Not firing before the fix -- the only marker in the repository was the legitimate one on PR #80 -- Worth noting these two findings were suppressed, so they created no review thread. Without the |
There was a problem hiding this comment.
🟢 Approval recommended
The changes consistently apply the shared native-command guard across tools, add host-sensitive tests/CI coverage, and introduce the review-scanning tool without altering crate codepaths.
Review details
Suppressed comments (1)
tools/scan-pr-reviews.ps1:180
- Invoke-GitHubJson discards gh stderr via Invoke-NativeStdout, so on a failure (non-zero exit) the Exit-Broken message can end up missing the real diagnostic text (which gh typically prints to stderr). Consider capturing merged stdout+stderr only on the failing path so the error message is actionable without compromising the JSON parse on success.
$text = Invoke-NativeStdout { gh @Arguments }
$code = Get-LastExitCode
if ($null -eq $code -or $code -ne 0) {
Exit-Broken "gh $($Arguments -join ' ') failed (exit $code): $($text -join ' ')"
}
- Files reviewed: 10/10 changed files
- Comments generated: 0 new
- Review effort level: Lite
… parsed output Copilot review 5137310730, whose finding is a regression I introduced two commits ago: routing `gh` through the stdout-only capture fixed the JSON corruption and silently emptied every broken-instrument message. Measured which stream `gh` actually uses, because it is not uniform and the difference decides the fix: REST 404 stdout has the JSON error body, stderr `gh: Not Found` network failure stdout EMPTY, stderr `error connecting to ...` usage error stdout EMPTY, stderr the usage text So the 404 case still reported -- which is what made this look fine locally -- while a bad host, a bad flag or an auth problem produced `failed (exit 1): ` with nothing after the colon. Blank exactly when the cause is least guessable, which is the shape a broken-instrument message exists to explain. Added `Invoke-NativeSplit`, which captures both streams separately: stdout stays clean for the parse, stderr is available for the report. Both `gh` call sites use it, preferring stderr and falling back to stdout, so the 404's JSON body is not lost either. The marker-post path is converted too -- it was merging, so a successful post could echo a warning as though it were the comment URL. **The first implementation was wrong on 5.1, and the cross-host suite caught it.** Redirecting stderr to a file (`2>$path`) looks like the clean way to keep the streams apart, and on PowerShell 7 it is. Windows PowerShell 5.1 writes the FORMATTED error record there instead -- `cmd.exe : to-err`, the offending source line, a caret ruler, CategoryInfo and FullyQualifiedErrorId -- so the diagnostic would have arrived wrapped in a stack trace of the helper itself. Two of the new cases failed on 5.1 and passed on 7. The shipped version merges with `2>&1` and partitions by RECORD TYPE, which is lossless on both: PowerShell wraps native stderr in ErrorRecords and leaves stdout as plain strings. Five cases added, including the one that names the defect -- a diagnostic survives when stdout is empty -- plus the 5.1 redirect hazard and an empty stderr arriving as `''` rather than `$null`, which would throw at the call sites under StrictMode. Eighteen cases now, passing on both hosts, and the `-Pr 999999` message is identical on each. Verified: exit contract intact on both hosts, sabotage suite passes on both, encoding 615 files clean, workflow references resolve. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Addressed in 2cc009b. The approval's one suppressed finding was a regression I introduced two Measured which stream
Worth recording: the first implementation used a file redirect ( Eighteen cases now, passing on both hosts, and the failure message is identical on each. |
|
Already fixed when this review ran. It reviewed 4b0134d and its one suppressed finding -- that The exact line it quotes no longer exists: Recorded here rather than left open, because a suppressed finding creates no thread and would |
There was a problem hiding this comment.
🟢 Approval recommended
The changes are cohesive, well-scoped to tooling/CI/design-notes, and the only issue found is a small comment/name mismatch that doesn’t affect behavior.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
tools/run-sabotage.ps1:219
- This comment says Get-RepoRoot goes through
Invoke-Native, but the code now usesInvoke-NativeStdout. Keeping the helper name accurate matters here because the choice (merge vs stdout-only) is part of the contract being explained.
- Files reviewed: 10/10 changed files
- Comments generated: 0 new
- Review effort level: Lite
Restatement drift, from an approving review. Converting `Get-RepoRoot` to the stdout-only capture changed the call and left the comment above it still saying "Through `Invoke-Native`", so the explanation named the merging helper while the code used the discarding one. That distinction is the contract being explained -- merge for a transcript, discard for output that is parsed -- so a wrong name there is worse than no name. Two comment blocks had also stacked up, the older one first, which is how the stale name survived being read. Merged into one that says why the call is guarded at all (any stderr redirect is terminating on 5.1, and this call's failure mode IS stderr) and separately why it is stdout-only (the output becomes the repository root, and a git warning merged into it would corrupt the path). Swept the class rather than the reported line: all 37 `Invoke-Native*` call sites across `tools/`, comparing the helper each one calls against the helper named in its own preceding comment block. Exactly one mismatch, the reported one. The other two converted sites read correctly. No behaviour change. Verified anyway on both hosts: the outside-a-git-repository case passes, the full sabotage suite passes, test-common passes, encoding and workflow-reference checks pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Addressed in c40a2cb. The suppressed finding was restatement drift: converting Swept the class rather than the reported line: all 37 No behaviour change; verified on both hosts anyway. |
There was a problem hiding this comment.
🔵 Needs a closer look
The new review-scanner misclassifies non-collaborator permission checks (HTTP 404) as “unverifiable,” which can emit a misleading “token lacks push access” warning and should be corrected before approval.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
tools/scan-pr-reviews.ps1:347
- Get-RetireAuthority treats ANY non-zero
gh api .../collaborators/<login>/permissionresult asunverifiable(and later prints the "token has no push access" warning). But the GitHub API returns 404 for a user who is not a collaborator, which is an ordinary "denied" outcome; classifying that asunverifiablewill produce a misleading warning whenever a non-collaborator posts a marker comment.
- Files reviewed: 10/10 changed files
- Comments generated: 0 new
- Review effort level: Lite
Brings back stage 3 of peeling this branch apart: the shared native-command guard and the Copilot review scanner (#81). Taken from this branch, then corrected over ten review rounds on the way through, so main holds the successor of every file it touches. Four conflicts, all resolved to main on that basis. Checked rather than assumed: every line unique to this branch is one those rounds replaced -- - common.ps1: the `try`/`finally` restoration that was dead code, because the assignment is function-local and the caller's value was never modified. - scan-pr-reviews.ps1: `throw`-based error paths that exited 1 and collided with the findings code, and the bare marker format that could be retired by quoting it in ordinary discussion. - test-common.ps1: the three cases that asserted the dead restoration and could not fail. - DESIGN-NOTES: the same claims in prose, plus the marker documentation, which main now states with the sentinel format and the write-access rule. Verified on both PowerShell hosts, since that is the whole subject of what merged: test-common and the sabotage suite pass under 7 and 5.1, the review scanner runs, encoding checks 636 files clean, workflow references resolve, and `cargo check --all-targets` is clean with no warnings. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Stage 3 of peeling #56 apart, after
#79 (the probe report sink) and
#80 (the long-path probe pair).
Nine files, no crate touched. Everything here is
tools/, its CI job, and the design note thatrecords the two decisions.
1. The native-command guard, shared
Under Windows PowerShell 5.1, a native command that writes to stderr while
$ErrorActionPreferenceisStopraises a terminating error when its stderr is redirected with2>&1. PowerShell 7 does not -- which is why this class of defect survives review and CI.run-numa-spikes.ps1had two such captures and no guard. The shape of that failure is what makes itworth a PR rather than a one-line fix:
Measured both ways against a deliberately uncompilable crate:
RemoteException, captured nothingWhy it is dot-sourced and not a module
The guard already existed as three hand-copied copies, so this shares it -- and how it is shared
is a correctness requirement.
A scriptblock carries the session state it was created in.
Invoke-Native { cargo build }buildsthat scriptblock in the caller's scope, so
& $Commandruns it there; a.psm1copy sets$ErrorActionPreferencein the module's scope and the flip never reaches the call. Measured withthe identical body in a module: under 5.1 seven of eight cases failed, while all eight passed under
PowerShell 7.
A module can be made to work via
$PSCmdlet.SessionState.PSVariable.Set(...), and that wasmeasured working on both hosts. It is rejected because the guard would then rest on a subtlety that
looks removable -- anyone simplifying it back to a plain assignment reintroduces a defect that still
passes on PowerShell 7 and in CI.
test-common.ps1runs its eight cases in the invoking host, re-invokes itself in the other, andtreats a missing host as a failure rather than a skip -- a single-host pass is not the claim it
exists to make. The sabotage job now runs under both
pwshandpowershell, because it ranpwsh-only, which is precisely why this was invisible.
run-mutants.ps1was checked for the same defect and needs no change -- it redirects nothing,confirmed by experiment on both hosts.
2. The Copilot review scanner
PR #56 has 197 Copilot reviews and nothing said which were dealt with. Findings arrive in two shapes
and only one has state:
<details>block and create nothread. A review is not a reactable object either:
POST /pulls/{n}/reviews/{id}/reactionsreturns 404 while the same call on an inline comment succeeds. So nothing anywhere records that
a suppressed finding was read.
-MarkProcessedcloses that gap with a marker comment:An HTML comment, so it does not render; on the pull request rather than in a file or a session, so it
survives a new machine, contributor, or agent session; read back by the script, so a handled review
stops being reported. A
-Summaryis required alongside it, because a marker with no account ofwhat was done is a claim with no evidence.
Exercised end to end on #80: six threads resolved, one suppressed-only review marked, re-scan clean
at exit 0.
Deliberately not included
Write-Reportis not consolidated. Six scripts define one and they are not duplicates --they differ in level vocabulary (
warn/warning,bad/error, plusgood,note,detail,heading) and in rendering, with two emitting GitHub Actions annotations and four emitting consolecolours. Merging them would change six tools' output to remove a duplication that is only apparent.
mainhas no M34 milestone -- it lives in the source branch'sdocumentation cluster -- so the stub and its archive entry travel with that stage rather than being
half-landed here.
Verification
Both hosts, run rather than reasoned about:
test-common.ps1-- 8 cases, passing on 7 and 5.1.test-run-sabotage.ps1-- all shards pass under both hosts.run-numa-spikes.ps1-- completes on both.staying green on 7, so the suite detects the regression it was written for.
check-encoding.ps1615 files clean;check-workflow-refs.ps156 references resolve.