Skip to content

fix(windows): stop a PowerShell BOM breaking hook rebuilds - #3086

Closed
rohit-jsfreaky wants to merge 1 commit into
Graphify-Labs:v8from
rohit-jsfreaky:fix/windows-bom-sidecar-markers
Closed

fix(windows): stop a PowerShell BOM breaking hook rebuilds#3086
rohit-jsfreaky wants to merge 1 commit into
Graphify-Labs:v8from
rohit-jsfreaky:fix/windows-bom-sidecar-markers

Conversation

@rohit-jsfreaky

Copy link
Copy Markdown
Contributor

Fixes #3028.

Summary

The Windows bootstrap writes the two sidecar markers with Out-File -Encoding utf8. On Windows PowerShell 5.1 that always prepends a UTF-8 BOM — utf8NoBOM only exists from PowerShell 6, and -NoNewline does nothing about it. So graphify-out/.graphify_python and .graphify_root start with EF BB BF.

Every reader then decodes them with utf-8, which keeps the BOM as U+FEFF, and str.strip() does not remove it — U+FEFF is not whitespace. The character rides into path handling and the post-commit rebuild dies with WinError 123, while hook install and hook status both keep reporting success. The graph silently goes stale unless someone opens ~/.cache/graphify-rebuild.log.

WinError 123 is the loud failure. There is a quiet one too: in _infer_merge_root the BOM makes an absolute path stop being drive-qualified, so Path(recorded).resolve() treats it as relative and silently joins it onto the cwd — no exception, just a wrong scan root:

D:\...\Graphify-Labs--graphify\<BOM>C:\Users\<user>\...\proj

Changes

Read side — the half that repairs installs already broken. Five call sites now decode with utf-8-sig, which strips an optional BOM and is a no-op on a clean file: hooks.py (both rebuild bodies), watch.py, reflect.py, build.py. Rewriting the markers alone would only help fresh runs; anyone already in this state stays broken until they re-run the bootstrap. With utf-8-sig they recover on the next commit.

Write side — stop producing the BOM. fragments/shell/powershell.md writes both markers through [System.IO.File]::WriteAllText with an explicit New-Object System.Text.UTF8Encoding $false, which emits no BOM and appends no newline — the same bytes the POSIX fragment already writes with echo >. Regenerated with python -m tools.skillgen + --bless; only the windows platform uses this fragment, so skill-windows.md and its expected/ snapshot are the only rendered artifacts that change.

Verification on the actual shell

Both forms run on Windows PowerShell 5.1.26100.9168, writing the same interpreter path:

form first 3 bytes BOM length
Out-File -Encoding utf8 -NoNewline (today) EF BB BF yes 26
WriteAllText + UTF8Encoding $false (this PR) 43 3A 5C (C:\) no 23

Exactly the three BOM bytes apart, and no trailing newline either way.

Tests

Four added, each confirmed to fail with the source changes reverted and the tests kept:

  • test_graphify_root_marker_with_a_utf8_bom_still_resolves — the repro: a marker written with a real EF BB BF prefix must still resolve to the true root through _infer_merge_root.
  • test_rebuild_bodies_tolerate_a_bom_in_graphify_root (both hook bodies) — the generated rebuild must read the marker BOM-tolerantly, since that is the site that produced WinError 123.
  • test_windows_skill_writes_marker_files_without_a_bom — the rendered Windows skill must not write either marker through a BOM-emitting Out-File.

One existing test needed updating rather than working around: test_rebuild_bodies_read_graphify_root asserted the literal read_text(encoding='utf-8'). Its stated intent is shell-quote safety inside the double-quoted launcher ("single quotes only"), which utf-8-sig still satisfies, so the literal moved with a comment explaining why the codec changed.

hooks 95 passed (2 pre-existing platform failures), skillgen 65 passed, build-merge 10 passed.

All five skillgen guards pass: --check (134 artifacts), --audit-coverage, --schema-singleton, --monolith-roundtrip, --always-on-roundtrip.

uv run --no-sync ruff check on every touched file — passed.

Full suite: 14 failed, 4985 passed, 41 skipped — the same 14 as a clean v8 checkout of this commit on this Windows machine (12 platform tests: fifo / unix-socket / hermes / gemini paths / watch; plus 2 FalkorDB integration tests failing against the local port with a redis ResponseError). Verified by running them in a detached worktree at 282976b and diffing the failure lists.

I have not run an end-to-end git commit against a BOM'd install; the evidence here is the byte-level PowerShell comparison above plus the reproduced path corruption.

Note: this touches graphify/skill-windows.md, which #2677 also touches — at line 645 there versus 120/122 here, so they should merge cleanly in either order.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Fixes WinError 123 rebuild failures on Windows PowerShell 5.1 (#3028) by decoding the .graphify_root marker with utf-8-sig everywhere it's read — _infer_merge_root, both hook rebuild bodies, _resolve_source_path, and watch's _StoredSourcePaths — so a stray UTF-8 BOM no longer rides into a path and gets silently treated as relative. Switches the Windows skill/powershell bootstrap to write .graphify_python and .graphify_root via WriteAllText with a BOM-less UTF8Encoding, since 5.1's Out-File -Encoding utf8 always emits a BOM; the reader change heals existing broken checkouts in place.

No blocking issues surfaced.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 1687 functions depend on the 613 functions this change touches.

Health — this change adds coupling hotspots:

  • new: _rebuild_code() — 98 callers, 50 callees
  • new: build_from_json() — 187 callers, 18 callees
  • new: build_merge() — 46 callers, 14 callees
  • new: to_obsidian() — 36 callers, 13 callees
  • new: extract_files_direct() — 17 callers, 20 callees
  • new: to_wiki() — 41 callers, 7 callees
  • new: extract_corpus_parallel() — 26 callers, 11 callees
  • new: _call_claude_cli() — 31 callers, 9 callees
  • …and 50 more — each is listed as a finding

Verification — 1687 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 1308 function(s) in the blast radius were not formally verified this run

Formal verification

Could not verify: Could not verify \_infer\_merge\_root.

The verifier did not have enough to check \_infer\_merge\_root, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `graph_path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_resolve\_source\_path.

The verifier did not have enough to check \_resolve\_source\_path, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `graph_path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_\_init\_\_.

The verifier did not have enough to check \_\_init\_\_, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

· 58 more finding(s) on lines outside this diff (see the check run).

@safishamsi

Copy link
Copy Markdown
Collaborator

Shipped in v0.9.50 via authorship-preserving cherry-pick so you keep contributor-graph credit. Thanks @rohit-jsfreaky! Release: https://github.com/Graphify-Labs/graphify/releases/tag/v0.9.50

@safishamsi safishamsi closed this Aug 25, 2026
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.

Windows PowerShell 5.1: Out-File -Encoding utf8 BOM in .graphify_python/.graphify_root breaks post-commit hook rebuild (WinError 123)

2 participants