Skip to content

fix(windows): create OpenCode runtime staging directories literally - #8162

Open
vaibhavsrv wants to merge 4 commits into
Osmantic:mainfrom
vaibhavsrv:fix/windows-opencode-runtime-literalpath
Open

vaibhavsrv wants to merge 4 commits into
Osmantic:mainfrom
vaibhavsrv:fix/windows-opencode-runtime-literalpath

Conversation

@vaibhavsrv

@vaibhavsrv vaibhavsrv commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes #8161. Fixes wildcard parameter binding failures across the entire Windows OpenCode installation, download, and archive extraction chain when ODS is installed into directories containing square brackets (e.g. C:\tools\[ods]\bin\opencode.exe or username paths like C:\Users\runner[1]\...).

Baseline Defect & Reproduction

When $Destination contains square brackets, standard PowerShell path cmdlets treat [ and ] as wildcard character class specifiers:

  1. In ods/installers/windows/lib/opencode-runtime.ps1, lines 23 and 25 created destination and staging directories via New-Item -ItemType Directory -Path $bin:
    New-Item : Cannot bind argument to parameter 'Path' because it is null or contains wildcard characters.
    
  2. In ods/installers/windows/lib/ui.ps1, Show-ProgressDownload executes curl.exe -o $partFile $Url. While curl writes the .part file successfully, line 426 evaluated if ($curlExit -eq 0 -and (Test-Path $partFile)) without -LiteralPath, returning $false and leaving release.zip.part un-promoted on disk. Line 427 then invoked Move-Item -Path $partFile, throwing wildcard binding errors on source.
  3. In ods/installers/windows/lib/ui.ps1, Invoke-DownloadWithRetry evaluated line 480 if ((Test-Path $Destination) -and ((Get-Item $Destination).Length -gt 0)) without -LiteralPath, returning false-negative $false.
  4. In ods/installers/windows/lib/detection.ps1, Test-ZipIntegrity checked archive existence using Test-Path $Path and Get-Item $Path, returning File not found for bracketed archives.
  5. In ods/installers/windows/lib/ui.ps1, Invoke-ExtractionWithRetry failed on New-Item -Path $parentDir and Expand-Archive -Path $ZipPath.

Surgical Fix Across Chain

Literal-path semantics are carried through the entire execution path without altering precedence or operational safeguards:

  1. ods/installers/windows/lib/opencode-runtime.ps1: Uses [System.IO.Directory]::CreateDirectory for $bin and $stage.
  2. ods/installers/windows/lib/ui.ps1:
    • In Show-ProgressDownload: Uses Test-Path -LiteralPath $partFile and Move-Item -LiteralPath $partFile -Destination $Destination -Force.
    • In Invoke-DownloadWithRetry: Uses Test-Path -LiteralPath $Destination and Get-Item -LiteralPath $Destination.
    • In Invoke-ODSHuggingFaceDownloadFallback: Uses Test-Path -LiteralPath $Destination and Get-Item -LiteralPath $Destination.
    • In Get-ODSHuggingFaceDownloadHelper: Uses Test-Path -LiteralPath $candidate.
    • In Invoke-ExtractionWithRetry: Uses Test-Path -LiteralPath, Remove-Item -LiteralPath, [System.IO.Directory]::CreateDirectory, Expand-Archive -LiteralPath, and Get-ChildItem -LiteralPath.
  3. ods/installers/windows/lib/detection.ps1: In Test-ZipIntegrity, uses Test-Path -LiteralPath $Path, Get-Item -LiteralPath $Path, and opens $fileInfo.FullName via [System.IO.Compression.ZipFile]::OpenRead.
  4. ods/tests/test-windows-opencode-runtime.ps1: Sourced directly against real production helpers (Show-ProgressDownload, Invoke-DownloadWithRetry, Test-ZipIntegrity, Invoke-ExtractionWithRetry), removing all helper stubs. Exercises real download, verification, extraction, and replacement for plain and bracketed paths (bracket-[test], [a]b, a[, ]a).

AI Assistance

AI drafted the fix and tests and ran the checks. The human author did not review each line before submission.

Release Lane

  • Stable hotfix targeting release/2.6.x
  • Mainline change targeting main
    • Targets Windows OpenCode runtime updater and shared download/extraction helpers.
  • Next-minor work targeting the next feature/minor release
  • Not sure; reviewer should help classify

Stable hotfix reason:

<!-- If this targets release/2.6.x, explain the current-stable user problem it fixes. -->

Changed Surface

  • Docs only
  • Tests only
  • Dashboard UI
  • Dashboard API / host agent
  • Installer / bootstrap / lifecycle
    • Modifies Windows OpenCode download and runtime extraction helpers (opencode-runtime.ps1, ui.ps1, detection.ps1).
  • Docker Compose / service manifests
  • Model routing / Hermes / capabilities
  • Network exposure / auth / proxy
  • Dependencies / runtime wiring

Risk And Validation

  • Risk level: High
  • Validation run:
    • git diff --check
      • Confirmed clean tree with zero trailing whitespace or merge conflict markers.
    • Markdown/link sanity for docs
    • Focused tests listed below
      • Full download, integrity, extraction, and update chain regressions in ods/tests/test-windows-opencode-runtime.ps1 covering plain and bracketed destination paths under PowerShell 7 and Windows PowerShell 5.1.
    • Dashboard lint/test/build
    • Extension audit / compose validation
    • Release-grade fleet or scoped hardware validation
      • Hosted GitHub Actions Windows runner (windows-latest / Microsoft Windows Server 2025, build 10.0.26100, image windows-2025-vs2026) running the full PowerShell test suite.
    • Stable-lane patch validation, if targeting release/2.6.x
    • Not required because:

Commands/results:

# Baseline failure reproduction (unpatched base):
# Destination: C:\...\bracket-[test]\bin\opencode.exe
# Error: New-Item : Cannot bind argument to parameter 'Path' because it is null or contains wildcard characters.
# Exit code: 1

# Download helper failure reproduction (round 1 head):
# Destination: C:\...\bracket-[test]\release.zip
# Behavior: curl exits 0, Test-Path $partFile returns False, Move-Item -Path fails, release.zip.part remains on disk.
# Result: Invoke-DownloadWithRetry returns False.

# Post-fix execution (full real chain):
./ods/tests/test-windows-opencode-runtime.ps1
# PASS Show-ProgressDownload plain succeeds
# PASS Plain download target exists
# PASS Plain .part file removed
# PASS Plain download bytes match fixture
# PASS Show-ProgressDownload bracketed succeeds
# PASS Bracketed download target exists
# PASS Bracketed .part file removed
# PASS Bracketed download bytes match fixture
# PASS Invoke-DownloadWithRetry [a]b succeeds
# PASS [a]b download target exists
# PASS [a]b .part file removed
# PASS [a]b download bytes match fixture
# PASS Invoke-DownloadWithRetry a[ succeeds
# PASS a[ download target exists
# PASS a[ .part file removed
# PASS Invoke-DownloadWithRetry ]a succeeds
# PASS ]a download target exists
# PASS ]a .part file removed
# PASS Curl failure rejected
# PASS Failed download destination not created
# PASS plain fresh install succeeds
# PASS plain fresh bytes correct
# PASS plain fresh staging cleaned
# PASS older binary upgraded
# PASS replacement bytes correct
# PASS current version reusable
# PASS checksum mismatch rejected
# PASS checksum failure preserves old binary
# PASS checksum failure staging cleaned
# PASS wrong staged version rejected
# PASS wrong version failure preserves old binary
# PASS wrong version staging cleaned
# PASS busy executable preserved without process termination
# PASS busy failure preserves old binary
# PASS custom config untouched
# PASS busy staging cleaned
# PASS bracketed destination upgraded
# PASS bracketed replacement bytes correct
# PASS bracketed staging cleaned
# PASS bracketed filename [a]b.exe upgraded
# PASS [a]b.exe replacement bytes correct
# PASS [a]b.exe staging cleaned
# PASS bracketed filename a[.exe upgraded
# PASS a[.exe replacement bytes correct
# PASS bracketed filename ]a.exe upgraded
# PASS ]a.exe replacement bytes correct
# PASS bracketed corrupt zip rejected
# PASS corrupt zip preserves old binary
# PASS corrupt zip staging cleaned
# PASS bracketed curl failure rejected
# PASS curl failure preserves old binary
# PASS curl failure staging cleaned

Operational Change Check

If this PR touches installer phases, bootstrap logic, compose stack generation,
service manifests, dashboard API control flows, Hermes, model routing, GPU or
runtime detection, lifecycle commands, host mutation, or network exposure, it
requires release-grade fleet validation before release unless the PR explains a
narrower equivalent.

  • This is not an operational change.
  • This is an operational change and validation is recorded above.
  • This is an operational change and validation is intentionally deferred for:

Notes For Reviewers

@vaibhavsrv
vaibhavsrv force-pushed the fix/windows-opencode-runtime-literalpath branch from 0cd1310 to 6e88e16 Compare October 25, 2026 12:46
@Lightheartdevs

Copy link
Copy Markdown
Collaborator

Thanks for fixing directory creation in OpenCode's staging path and preserving the existing upgrade safeguards.

Advisory status: not ready yet

Quickest path to merge:

  • Carry literal-path handling through the real extraction chain called by ods/installers/windows/lib/opencode-runtime.ps1. Test-ZipIntegrity in ods/installers/windows/lib/detection.ps1 still uses wildcard-aware Test-Path $Path and Get-Item $Path; Invoke-ExtractionWithRetry in ods/installers/windows/lib/ui.ps1 also uses wildcard-aware archive/destination operations. With a valid ZIP at bracket-[test]/bin/.ods-update-fixture/release.zip, the actual helpers return File not found and extraction fails, while the same ZIP under a plain path succeeds. The directory fix therefore does not yet make the advertised installation path work.
  • Extend ods/tests/test-windows-opencode-runtime.ps1 with a real fixture ZIP and the production validation/extraction helpers. The current Invoke-ExtractionWithRetry stub creates the directory directly, so the new bracketed-path test bypasses this failure. Verify installation or upgrade, replacement bytes, and staging cleanup through that real path.

What I checked:

  • Exact-head diff, callers, repository installer contracts, discussion, CI wiring, and git diff --check; all 73 hosted checks pass.
  • The supplied runtime test passes all 14 assertions under PowerShell 7. An additional PowerShell 7 probe using the exact-head production ZIP helpers passes for a plain path and fails for the bracketed path above. This used local fixture files, with no download or installed-runtime changes.

Thanks again; exercising the real extraction path should make the regression test protect the intended fix.

In ods/installers/windows/lib/opencode-runtime.ps1, Install-ODSOpenCode manages downloading, verifying, and updating the OpenCode binary on Windows. When Destination contains square brackets (e.g. C:\tools\[ods]\bin\opencode.exe), directory creation and the subsequent archive extraction chain fail on wildcard-sensitive parameter binding.

Carry literal-path handling through:
1. opencode-runtime.ps1: Use [System.IO.Directory]::CreateDirectory for destination bin and staging directories.
2. detection.ps1: Use -LiteralPath and FullName in Test-ZipIntegrity to validate archives in bracketed paths.
3. ui.ps1: Use -LiteralPath and [System.IO.Directory]::CreateDirectory in Invoke-ExtractionWithRetry for archive cleanup, directory creation, Expand-Archive, and extraction verification.

Extend ods/tests/test-windows-opencode-runtime.ps1 with a real fixture ZIP and production extraction helpers, verifying plain and bracketed destination paths.
@vaibhavsrv
vaibhavsrv force-pushed the fix/windows-opencode-runtime-literalpath branch from 6e88e16 to 58c3189 Compare October 26, 2026 02:11
@vaibhavsrv

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review and guidance @Lightheartdevs.

I have carried -LiteralPath handling throughout the extraction and validation chain, and updated the regression test to exercise the real production helpers:

  1. ods/installers/windows/lib/detection.ps1:

    • Test-ZipIntegrity now checks archive presence using Test-Path -LiteralPath $Path and Get-Item -LiteralPath $Path, and opens $fileInfo.FullName directly via [System.IO.Compression.ZipFile]::OpenRead to avoid wildcard expansion on bracketed archive paths.
  2. ods/installers/windows/lib/ui.ps1:

    • Invoke-ExtractionWithRetry now uses -LiteralPath for Test-Path, Remove-Item, Expand-Archive, and destination verification (Get-ChildItem -LiteralPath).
    • Replaced New-Item -ItemType Directory -Path $parentDir with [System.IO.Directory]::CreateDirectory($parentDir).
  3. ods/tests/test-windows-opencode-runtime.ps1:

    • Removed the Invoke-ExtractionWithRetry mock stub.
    • Dot-sources the production detection.ps1 and ui.ps1 helpers.
    • Generates valid fixture ZIP archives (New-FixtureZip) and runs the actual integrity verification and extraction logic for both standard and bracketed destination paths (bracket-[test]/bin/opencode.exe).
    • Asserts binary upgrade, byte verification, and staging cleanup through the real extraction pipeline.

All 73 hosted CI checks (including powershell-lint (windows-latest) running all 14 assertions on Windows Server 2022) are green.

@Lightheartdevs

Copy link
Copy Markdown
Collaborator

Thanks for following through on the real ZIP validation and extraction path; the bracketed-path regression now exercises those helpers successfully.

Advisory status: not ready yet

Quickest path to merge:

  • Carry literal-path handling through the earlier download step in ods/installers/windows/lib/ui.ps1: Show-ProgressDownload still uses Test-Path $partFile and Move-Item -Path $partFile (lines 426–433), and Invoke-DownloadWithRetry checks the final destination with wildcard-aware Test-Path/Get-Item (line 480). With a local file:// fixture, curl exits successfully for both paths, but bracket-[test]/release.zip returns false and leaves release.zip.part; the plain-path control succeeds. Install-ODSOpenCode calls this chain before extraction, so the advertised bracketed installation still fails. Use -LiteralPath for these source-file checks and moves.
  • Extend ods/tests/test-windows-opencode-runtime.ps1 to exercise the production download helpers, substituting only the transport or using a local fixture. Its current Invoke-DownloadWithRetry stub bypasses the remaining failure. Assert success, final bytes, and removal of the .part file for plain and bracketed destinations.

What I checked:

  • Exact-head diff, production call chain, prior discussion, CI wiring, and git diff --check; all 73 hosted checks pass.
  • All 14 supplied assertions pass locally on PowerShell 5.1 and 7. The separate production-download probe reproduces the failure on both versions, without external downloads or installed-runtime changes.

The extraction fix is working; completing the download path should close the remaining gap.

Carry -LiteralPath handling through Show-ProgressDownload, Invoke-DownloadWithRetry,
and Invoke-ODSHuggingFaceDownloadFallback to prevent wildcard parameter binding
failures when downloading files into bracketed destination directories.

Extend ods/tests/test-windows-opencode-runtime.ps1 with unstubbed production
download helpers against a local file:// fixture, plain-path regression checks
for the GGUF model download caller, and comprehensive bracketed-path coverage.
@vaibhavsrv

Copy link
Copy Markdown
Contributor Author

Thanks @Lightheartdevs. Pushed as new commits only (no force-pushes, no history rewrite) to head a1868b716c026b4850fa789ebf932822a94f61f5.

All 73 hosted CI checks are passing green on GitHub Actions (including Lint PowerShell on windows-latest / Windows Server 2025 build 10.0.26100, image windows-2025-vs2026 in 9m0s, and integration-smoke in 14m32s).

Sites updated across the OpenCode install, download, and extraction chain:

  • opencode-runtime.ps1:23,25: [System.IO.Directory]::CreateDirectory for bin and staging paths.
  • detection.ps1:544,552,569: -LiteralPath for Test-Path / Get-Item and $fileInfo.FullName in [System.IO.Compression.ZipFile]::OpenRead.
  • ui.ps1:203,395,426-433,480,537-552: -LiteralPath across download helper resolution, download fallback, Show-ProgressDownload .part check/move, Invoke-DownloadWithRetry destination probe, and Invoke-ExtractionWithRetry.
  • ods/tests/test-windows-opencode-runtime.ps1: Removed stubs; directly exercises production download and extraction helpers (Show-ProgressDownload, Invoke-DownloadWithRetry, Test-ZipIntegrity, Invoke-ExtractionWithRetry) using local fixtures and curl transport across 52 assertions covering plain and bracketed destinations (bracket-[test], [a]b, a[, ]a), .part removal, final byte validation, curl failure injection, and GGUF caller plain-path regression.

@Lightheartdevs

Copy link
Copy Markdown
Collaborator

Thanks for carrying literal-path handling through the download step as well as ZIP verification and extraction; this closes the gap from the previous advisory.

Advisory status: looks merge-ready

What looks good:

  • ods/installers/windows/lib/ui.ps1 now checks and promotes bracketed .part paths literally, and detection.ps1 opens the resolved archive path. The real download/extraction helpers are exercised rather than bypassed.
  • ods/tests/test-windows-opencode-runtime.ps1 covers plain and bracketed destinations, replacement bytes and staging cleanup, plus checksum, staged-version, corrupt-ZIP, download and busy-file failures that preserve the previous binary.

What I checked:

  • Current-head diff, surrounding production helpers, prior discussion, CI wiring and git diff --check; all 73 reported GitHub checks passed.
  • Independently ran the supplied test on Windows PowerShell 5.1: all 56 assertions passed. This used local fixture archives and the test's transport/native-version substitutions; it did not install or run an upstream OpenCode release.

Thanks for following through on the complete path.

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: OpenCode runtime staging directory creation fails on bracketed install paths

2 participants