Skip to content

fix(cli): make temporary directory cleanup non-fatal in the Windows install script - #135

Merged
AshGodfrey merged 4 commits into
mainfrom
ash/cli-install-ps1-cleanup-nonfatal
Oct 22, 2026
Merged

AshGodfrey merged 4 commits into
mainfrom
ash/cli-install-ps1-cleanup-nonfatal

Conversation

@AshGodfrey

@AshGodfrey AshGodfrey commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The generated scripts/install.ps1 deleted its download directory with Remove-Item -Recurse inside a finally block. On machines where $env:TEMP resolves to an 8.3 short path (a user profile such as C:\Users\USERNA~1.EXT), Windows PowerShell's filesystem provider fails that delete with An object at the specified path ... does not exist (see PowerShell/PowerShell#21070 and #7760). The exception propagated to the top-level handler and was reported as Installation failed even though the binary and PATH update had already succeeded.

Changes

  • Delete the temporary directory via [System.IO.Directory]::Delete($path, $true) instead of Remove-Item -Recurse. The .NET call does not go through the PowerShell filesystem provider, so it tolerates short-name path components.
  • Check for the directory with Test-Path -LiteralPath to match the delete call.
  • Wrap cleanup in its own try/catch and emit a warning naming the leftover folder, so a cleanup failure can no longer mask a completed install.

Testing

Regenerated the review CLI SDK; the only output change is scripts/install.ps1 and its gen.lock checksum. No PowerShell runtime is available locally, so the script was not executed.


Summary by cubic

Fixes the Windows install script so a failed temporary directory cleanup no longer reports a completed installation as failed.

  • The old cleanup used Remove-Item -Recurse, which fails when $env:TEMP resolves to an 8.3 short name like C:\Users\USERNA~1.EXT.
  • Uses Test-Path -LiteralPath and deletes via [System.IO.Directory]::Delete, which tolerates short paths, and wraps cleanup in a try/catch that prints a warning instead of throwing.
  • Adds a CI job that runs a new scripts/test-windows-installer.ps1 smoke test against the generated installer under both Windows PowerShell and PowerShell 7.
  • Updates the release snapshot tests-checklist checksum and adds a changeset entry; the script was not executed locally before the CI job was added.

Testing

  • The new CI job exercises the installer offline: it checks the installed binary, the download URL, and that a held-open temporary file produces a warning without failing the install.
  • On non-Windows hosts the held-file case is skipped because open files can be deleted there.

Written for commit 3771d0d. Summary will update on new commits.

View guided diff Turn on auto-fix

…nstall script

The installer deleted its download directory with Remove-Item -Recurse inside
a finally block. On machines where TEMP resolves to an 8.3 short path (a user
profile such as C:\Users\USERNA~1.EXT), Windows PowerShell's filesystem
provider fails that delete with "An object at the specified path ... does not
exist", and the throw was reported as "Installation failed" after the binary
and PATH were already in place.

Resolve TEMP to its long path before building the temporary directory, delete
it through System.IO.Directory.Delete, and downgrade any cleanup error to a
warning so a leftover temporary folder never masks a completed install.
@AshGodfrey
AshGodfrey requested a review from a team as a code owner October 22, 2026 16:42

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 4 files

Shadow auto-approve: would not auto-approve because issues were found.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread templates/templates/cli/release/install.ps1.stmpl Outdated
Comment thread templates/templates/cli/release/install.ps1.stmpl Outdated
…EMP path

Resolving TEMP through Get-Item threw before the cleanup guard when the
directory did not exist, and its FullName does not reliably expand 8.3
short names. The guarded Directory.Delete already tolerates short paths.

@cubic-dev-ai cubic-dev-ai 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.

0 issues found across 3 files (changes from recent commits).

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Shadow auto-approve: would not auto-approve. Auto-approval blocked by 1 unresolved issue from previous reviews.

View guided diff | Turn on auto-fix | Re-trigger cubic

@cubic-dev-ai cubic-dev-ai 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.

0 issues found across 1 file (changes from recent commits).

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Shadow auto-approve: would not auto-approve. Auto-approval blocked by 1 unresolved issue from previous reviews.

View guided diff | Turn on auto-fix | Re-trigger cubic

@2ynn 2ynn left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM.

Could we also add a CI smoke test that verifies extraction/copying for the zSDKs/sdk-cli/scripts/install.ps1 ?

test-windows-installer:
  runs-on: windows-latest
  strategy:
    fail-fast: false
    matrix:
      shell: [powershell, pwsh]
  steps:
    - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
    - name: Test generated Windows installer
      shell: ${{ matrix.shell }}
      run: ./scripts/test-windows-installer.ps1

@AshGodfrey

Copy link
Copy Markdown
Contributor Author

Added in 3771d0d: test-cli-windows-installer runs scripts/test-windows-installer.ps1 on windows-latest under both powershell and pwsh, gated on the CLI target like the other CLI jobs and folded into the test-cli aggregate.

The script runs the generated zSDKs/sdk-cli/scripts/install.ps1 offline: Invoke-WebRequest and Invoke-RestMethod are replaced with local functions that serve a locally built archive, then it checks the binary lands in CLI_INSTALL_DIR with the archived content, the download URL names the pinned version, and the temporary directory is removed. A second run holds the extracted binary open so directory removal fails, and asserts the install still succeeds with the new warning.

@AshGodfrey
AshGodfrey enabled auto-merge October 22, 2026 19:10

@cubic-dev-ai cubic-dev-ai 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.

3 issues found across 2 files (changes from recent commits).

Confidence score: 3/5

  • scripts/test-windows-installer.ps1 leaves each sandbox install directory in the persistent User PATH, so repeated runs keep adding stale entries. Restore the original User PATH after the tests.
  • The default InstallerPath in scripts/test-windows-installer.ps1 uses Windows-only separators, so the script may not find the installer under pwsh on Linux or macOS. Use forward slashes.
  • The sandbox roots in scripts/test-windows-installer.ps1 are left behind, and the second root can end up nested inside the first because GetTempPath() changes during the run.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="scripts/test-windows-installer.ps1">

<violation number="1" location="scripts/test-windows-installer.ps1:11">
P2: The default `InstallerPath` uses backslash separators, which only resolve on Windows. Use forward slashes so the path also resolves under pwsh on Linux/macOS, where this script is meant to run.</violation>

<violation number="2" location="scripts/test-windows-installer.ps1:29">
P3: Sandbox roots are never deleted, and the second sandbox is created inside the first one's temp directory because `GetTempPath()` returns the TEMP value that `Invoke-Installer` already changed. Build the root from the original temp path captured once at script start, and remove the root in the `finally` block after disposing the handle.</violation>

<violation number="3" location="scripts/test-windows-installer.ps1:57">
P2: Running the installer here writes the sandbox install directory into the persistent User PATH, and nothing restores it. Each run adds another temp `installer-test-<guid>` entry. Snapshot the User PATH before the tests and restore it in the `finally` block.</violation>
</file>

Shadow auto-approve: would not auto-approve because issues were found.

View guided diff | Turn on auto-fix | Re-trigger cubic

}
$global:InstallerTestArchive = $Sandbox.Archive
$global:InstallerTestDownloads = @()
& $InstallerPath *>&1 | ForEach-Object { "$_" }

@cubic-dev-ai cubic-dev-ai Bot Oct 22, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Running the installer here writes the sandbox install directory into the persistent User PATH, and nothing restores it. Each run adds another temp installer-test-<guid> entry. Snapshot the User PATH before the tests and restore it in the finally block.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At scripts/test-windows-installer.ps1, line 57:

<comment>Running the installer here writes the sandbox install directory into the persistent User PATH, and nothing restores it. Each run adds another temp `installer-test-<guid>` entry. Snapshot the User PATH before the tests and restore it in the `finally` block.</comment>

<file context>
@@ -0,0 +1,113 @@
+    }
+    $global:InstallerTestArchive = $Sandbox.Archive
+    $global:InstallerTestDownloads = @()
+    & $InstallerPath *>&1 | ForEach-Object { "$_" }
+}
+
</file context>
Fix with cubic

#>
[CmdletBinding()]
param(
[string]$InstallerPath = (Join-Path $PSScriptRoot '..\zSDKs\sdk-cli\scripts\install.ps1')

@cubic-dev-ai cubic-dev-ai Bot Oct 22, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: The default InstallerPath uses backslash separators, which only resolve on Windows. Use forward slashes so the path also resolves under pwsh on Linux/macOS, where this script is meant to run.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At scripts/test-windows-installer.ps1, line 11:

<comment>The default `InstallerPath` uses backslash separators, which only resolve on Windows. Use forward slashes so the path also resolves under pwsh on Linux/macOS, where this script is meant to run.</comment>

<file context>
@@ -0,0 +1,113 @@
+#>
+[CmdletBinding()]
+param(
+    [string]$InstallerPath = (Join-Path $PSScriptRoot '..\zSDKs\sdk-cli\scripts\install.ps1')
+)
+
</file context>
Suggested change
[string]$InstallerPath = (Join-Path $PSScriptRoot '..\zSDKs\sdk-cli\scripts\install.ps1')
[string]$InstallerPath = (Join-Path $PSScriptRoot '../zSDKs/sdk-cli/scripts/install.ps1')
Fix with cubic

}

function New-InstallerSandbox {
$root = Join-Path ([System.IO.Path]::GetTempPath()) "installer-test-$([guid]::NewGuid())"

@cubic-dev-ai cubic-dev-ai Bot Oct 22, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: Sandbox roots are never deleted, and the second sandbox is created inside the first one's temp directory because GetTempPath() returns the TEMP value that Invoke-Installer already changed. Build the root from the original temp path captured once at script start, and remove the root in the finally block after disposing the handle.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At scripts/test-windows-installer.ps1, line 29:

<comment>Sandbox roots are never deleted, and the second sandbox is created inside the first one's temp directory because `GetTempPath()` returns the TEMP value that `Invoke-Installer` already changed. Build the root from the original temp path captured once at script start, and remove the root in the `finally` block after disposing the handle.</comment>

<file context>
@@ -0,0 +1,113 @@
+}
+
+function New-InstallerSandbox {
+    $root = Join-Path ([System.IO.Path]::GetTempPath()) "installer-test-$([guid]::NewGuid())"
+    foreach ($name in 'temp', 'localappdata', 'install', 'payload') {
+        New-Item -ItemType Directory -Path (Join-Path $root $name) -Force | Out-Null
</file context>
Fix with cubic

@AshGodfrey
AshGodfrey added this pull request to the merge queue Oct 22, 2026
Merged via the queue into main with commit dd4b186 Oct 22, 2026
24 checks passed
@AshGodfrey
AshGodfrey deleted the ash/cli-install-ps1-cleanup-nonfatal branch October 22, 2026 19:17
@github-actions github-actions Bot locked and limited conversation to collaborators Oct 22, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants