fix(install): read native child logs with an explicit UTF-8 codec - #90510
fix(install): read native child logs with an explicit UTF-8 codec#90510yoggydev wants to merge 1 commit into
Conversation
Reviewed the diff. Thoroughly done: all four native-child log reads now state `-Encoding UTF8`, the reasoning lives as a comment at the redirect site where the BOM-less bytes are created, and the test module is unusually good — measured PS 5.1 behavior on ja-JP recorded in the docstring, an explicit out-of-scope statement for `Tee-Object`'s BOM'd UTF-16LE logs so nobody "finishes the job" into a misdecode, and a premise pin that `[Console]::OutputEncoding` remains display-only. Nit: the regression test matches four specific `Get-Content` shapes by regex, so a future read written in a different parameter order (`Get-Content -Raw -Path $logPath`) slips through uncovered. A stronger and simpler invariant: assert every `Get-Content` line in install.ps1 contains `-Encoding`, with a tiny named allowlist for the Tee-Object-fed reads — that fails loudly on any new unannotated read regardless of argument order. No blocking issues found. |
What does this PR do?
_Invoke-NativeWithTimeouthas cmd.exe redirect a child's stdout+stderr into a log file:No PowerShell decode happens on the way in, so that file holds the child's own bytes and carries no BOM. The children here are Node-based — npm, npx, playwright — and Node writes UTF-8.
Four reads of those logs do not state a codec:
Windows PowerShell 5.1's
Get-Contentsniffs a BOM, and falls back to the machine ANSI code page when there isn't one.Measured on ja-JP Windows 11 (ACP=932), Windows PowerShell 5.1.26100.9168. The same string written both ways, then read back:
The BOM is the whole difference. Logs this script writes itself go through
Out-File -Encoding utf8and carry one, so they read back correctly. Logs a child writes do not.Where it lands.
_Drain-NewLinesis the live-tail helper — it reads the log and prints it:Near the top of the script there is
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new(), whose own comment calls it "a DISPLAY-only fix ... only what the user sees in their terminal". Those two are on the same path and the read runs first, so on a CJK host that display fix never receives intact text from this stream — the one_Invoke-NativeWithTimeoutexists to preserve, because "on a fresh VM the install is 1-3 minutes; total silence is indistinguishable from a hang".$errTextat the failure branch is the same file. That is the text a user is shown, and told to copy, at the moment an install fails.Scope — two reads deliberately left alone.
$npmLogand$buildLogare written throughTee-Object -FilePath, which on 5.1 writes UTF-16LE with a BOM. Measured: a single"あ"teed to a file givesFF FE 42 30 0D 00 0A 00.Get-Contentdetects that BOM, so those reads are already correct and adding-Encoding UTF8to them would break them. The four changed here are exactly the ones whose file was written by a native child.Note on the linter.
scripts/check-windows-footguns.pynever sees this file:should_scan_file()returnsTrueonly for.py,.pyw,.pyi. No.ps1in the repository is scanned, including the Windows installer itself.Related Issue
No separate issue — reporting it here with the measurement. Same class as #89442 / #89468 ("Affected Windows users lose diagnostic text from native commands"), one layer out: this is the installer, before the agent exists to lose anything.
Type of Change
Changes Made
scripts/install.ps1— the fourGet-Contentreads of a natively-written log pass-Encoding UTF8: the live tail and the failure output in_Invoke-NativeWithTimeout's consumers, the playwright log, and the npm debug-log tail.scripts/install.ps1— a comment on thecmd.exeredirect in_Invoke-NativeWithTimeoutrecording why any reader of that file has to state a codec, and that[Console]::OutputEncodingdoes not cover it.tests/test_install_ps1_native_log_encoding.py— four tests: every natively-written log read states a codec; the redirect helper documents the constraint; the console fix is still display-only (so this is revisited rather than silently kept passing if that changes); and theTee-Objectsites stay out of scope so nobody "finishes the job" onto a UTF-16 file.How to Test
Output above is from that run.
FF FE 42 30 0D 00 0A 00— UTF-16LE with a BOM, which is why the twoTee-Objectreads are not touched.python -m pytest tests/test_install_ps1_*.py -qacross all fourteen install.ps1 test files, including the four added here:The one Windows failure is
test_install_ps1_venv_process_tree.py::test_venv_sweep_stops_managed_runtime_children_but_not_unrelated_processes, which runsinstall.ps1 -Stage venvfor real and stops on the installer's own precondition —{"ok":false,"reason":"uv is not installed. Run install.ps1 -Stage uv first."}. That host has nouv; the same test is skipped off-Windows. Nothing in this change touches that path.install.ps1with the new tests kept: 2 failed / 2 passed. The two that pass either way are the scope statements — they assert facts about[Console]::OutputEncodingandTee-Objectthat this PR does not change, and exist so a later edit cannot quietly invalidate the premise.Note on the checklist below: I ran the install.ps1 test files and the comparison above, not the full
pytest tests/ -q, so I left that box unchecked rather than claiming it.Checklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests passDocumentation & Housekeeping
docs/, docstrings) — or N/Acli-config.yaml.exampleif I added/changed config keys — or N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/AScreenshots / Logs
I have a ja-JP (ACP=932) Windows host and can measure anything else on that locale.
(Measured on my host. Drafted with Claude.)