Skip to content

fix(path): preserve the registry value type when rewriting PATH - #296

Merged
CalvinAllen merged 1 commit into
mainfrom
fix/path/preserve-registry-type
Oct 23, 2026
Merged

CalvinAllen merged 1 commit into
mainfrom
fix/path/preserve-registry-type

Conversation

@CalvinAllen

Copy link
Copy Markdown
Contributor

Description

Windows stores the User and System PATH as REG_EXPAND_SZ, and expands %VAR% entries such as %SystemRoot%\system32 only in a value of that type. Every place dtvem rewrote PATH wrote REG_SZ instead, so those entries stayed literal and their directories dropped off PATH for new processes. For the System PATH, written by dtvem init on a system install, that includes %SystemRoot%\system32 itself.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Code refactoring (no functional changes)
  • Performance improvement
  • Test coverage improvement
  • CI/CD or build process change

Related Issues

Resolves #295

Changes Made

  • One type-preserving writer. writePathValue (internal/path/registry_windows.go) writes REG_EXPAND_SZ when the value had that type, or when the new value holds a %VAR% reference. That's the rule Windows' own environment editor uses, so an already converted value is repaired on its next write. A missing PATH is created as REG_EXPAND_SZ.

  • All three Go call sites use it:

    • dtvem init user install (modifyUserPath);
    • dtvem init system install (modifySystemPath);
    • the doctor stale-shims fix (RemoveStaleShimsFromUserPath).

    Their logic moved into prependShimsDir and removeStaleShims, which take a small key interface so tests run against a fake. modifyUserPath and modifySystemPath were near-duplicates and now share modifyPath; their messages are unchanged.

  • install.ps1, a fourth writer the issue didn't list. It read the User PATH with [Environment]::GetEnvironmentVariable, which expands %VAR% entries, then wrote it with SetEnvironmentVariable, which writes REG_SZ. That froze entries like %USERPROFILE%\.dotnet\tools into fixed paths and changed the type. The new Add-ToUserPath edits HKCU\Environment directly: it reads the raw value, checks for the install folder against the expanded entries (so a %USERPROFILE% form counts as present), and writes REG_EXPAND_SZ. Send-EnvironmentChange sends the WM_SETTINGCHANGE broadcast that SetEnvironmentVariable used to.

  • New dtvem doctor check, path-registry-type. It flags a User or System PATH stored as REG_SZ that holds %VAR% references, since anyone who ran dtvem init or the stale-shims fix may already have one.

    • --fix rewrites the User PATH as REG_EXPAND_SZ with its entries unchanged.
    • The System PATH needs an elevated terminal, so the resolution shows the PowerShell command for it.
    • On other platforms the check passes with "PATH is not stored in the registry on this platform".
  • CLAUDE.md: a note to write PATH only through writePathValue.

Testing

Test Environment

  • OS: Windows 11 (plus GOOS=linux vet and GOOS=darwin build)
  • Go Version: 1.25.4 (module targets 1.23)
  • PowerShell: Windows PowerShell 5.1.26100 and PowerShell 7.6.8

Test Steps

  1. ./rnr check: gofmt, golangci-lint (0 issues), all 27 packages pass.
  2. internal/path/registry_windows_test.go (Windows only):
    • writePathValue: keeps REG_EXPAND_SZ, keeps REG_SZ without references, upgrades REG_SZ that has references, and doesn't treat a lone % as a reference.
    • readPathValue: a missing value reads as empty REG_EXPAND_SZ.
    • prependShimsDir: ordering, stale-entry removal and type kept, upgraded or created; it doesn't write after a read error.
    • removeStaleShims: keeps the type, and doesn't rewrite when nothing is stale.
    • repairPathType.
    • One real-registry round trip, against a scratch key under HKCU\Software that the test deletes, never the real environment.
  3. internal/doctor/check_path_registry_type_test.go: the not-Windows, fine, user-only, system-only and both cases; the fix wiring; a read error; registration.
  4. install.ps1's Add-ToUserPath, loaded into a harness pointed at a scratch HKCU\Software key, under Windows PowerShell 5.1 and PowerShell 7.
  5. A read-only dtvem doctor --no-fix on the real machine.

Test Results

Add-ToUserPath, identical in Windows PowerShell 5.1 and PowerShell 7:

Starting Path Result Type
C:\tools;%USERPROFILE%\.dotnet\tools (REG_EXPAND_SZ) install folder prepended ExpandString
same folder added again not added ExpandString
folder already present as %USERPROFILE%\.dotnet\tools not added ExpandString
C:\tools (REG_SZ) prepended ExpandString
no Path value created ExpandString

The real-machine dtvem doctor --no-fix reported ✓ PATH registry values expand their %VAR% entries.

Checklist

  • My code follows the Google Go Style Guide
  • I have run golangci-lint run and fixed all issues (includes gofmt, govet, and 40+ linters)
  • I have run go mod tidy to clean up dependencies (no dependency changes in this PR)
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes (go test ./...)
  • I have commented my code, particularly in hard-to-understand areas
  • I have updated the documentation (README.md, CONTRIBUTING.md, etc.) if needed
  • My changes generate no new warnings or errors
  • I have checked my code works on multiple platforms (Windows, macOS, Linux) if applicable (Windows tested; Linux vet and macOS build locally; CI covers all three)
  • I have added appropriate error handling
  • I have followed conventional commit format in my commit messages

Breaking Changes

None. Writing REG_EXPAND_SZ matches what Windows itself uses for PATH.

Additional Notes

  • PSScriptAnalyzer: I couldn't run it locally (not installed here, and Docker is stopped). The script-lint workflow runs it on this PR with CI's settings.
  • Not changed: the issue's other note, that stale-shims-path treats any .dtvem\shims entry that doesn't match the current DTVEM_ROOT as stale. That's a design question about multiple roots and can be its own issue.

Windows stores the User and System PATH as REG_EXPAND_SZ, and only
expands %VAR% entries such as %SystemRoot%\system32 in a value of that
type. Every place dtvem rewrote PATH wrote REG_SZ instead, leaving those
entries literal and their directories off PATH for new processes:

- dtvem init (modifyUserPath, modifySystemPath) and the doctor
  stale-shims fix (RemoveStaleShimsFromUserPath) read PATH with
  GetStringValue, dropped the type it returned, and wrote it back with
  SetStringValue. For the System PATH that would drop %SystemRoot%\
  system32 itself.
- install.ps1 read the User PATH with GetEnvironmentVariable, which
  expands %VAR% entries, and wrote it with SetEnvironmentVariable, which
  writes REG_SZ, freezing those entries and changing the type.

PATH is now written through writePathValue, which keeps REG_EXPAND_SZ
when the value had it or when it holds a %VAR% reference (the rule
Windows' own environment editor uses), so a converted value is repaired
on the next write. The three call sites share prependShimsDir and
removeStaleShims, which take a small key interface so tests run against
a fake rather than the real environment; modifyUserPath and
modifySystemPath, which were duplicates, now share modifyPath.

install.ps1 edits HKCU\Environment directly: it reads the raw value,
checks for the install directory against the expanded entries, writes
REG_EXPAND_SZ, and sends the WM_SETTINGCHANGE broadcast that
SetEnvironmentVariable used to.

A new dtvem doctor check, path-registry-type, flags a User or System
PATH stored as REG_SZ that holds %VAR% references. --fix rewrites the
User PATH as REG_EXPAND_SZ; the System PATH needs an elevated terminal,
so doctor shows the PowerShell command for it.
@CalvinAllen
CalvinAllen merged commit 2e1686c into main Oct 23, 2026
16 checks passed
@CalvinAllen
CalvinAllen deleted the fix/path/preserve-registry-type branch October 23, 2026 14:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

fix(path): preserve the registry value type when rewriting PATH on Windows

1 participant