From c469a05ce58b0f269b9750dc6e9a857abcff7ccf Mon Sep 17 00:00:00 2001
From: Teknium <127238744+teknium1@users.noreply.github.com>
Date: Thu, 7 May 2026 18:00:59 -0700
Subject: [PATCH] fix(install.ps1): validate existing repo via git itself +
clean up broken stubs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
teknium1 hit "fatal: not in a git directory" on re-install when the previous
install left a $InstallDir\.git stub that Test-Path matched but git didn't
recognize (three "fatal: not a git repository" lines, then the script
exited before touching anything).
Two bugs:
1. Test-Path "$InstallDir\.git" was a weak gate — it matches .git
whether it's a directory, file, symlink, submodule gitfile, OR a
broken stub from a failed previous Remove-Item. Replaced with a
real repo probe: Push-Location + git rev-parse --is-inside-work-tree
+ $LASTEXITCODE check. If git itself can't see a repo, we treat
the directory as not-a-repo and fall through to fresh clone.
2. The original update path ignored $LASTEXITCODE. fetch/checkout/pull
all emitted fatals but the script kept going. Now each command
checks $LASTEXITCODE and throws with an explicit message.
Also: when the directory exists but isn't a valid repo, the new code
wipes it (Remove-Item -ErrorAction Stop) and falls through to fresh
clone, instead of dying with the old "Directory exists but is not a git
repository" error. If the wipe itself fails (file locked, hermes still
running), we throw with a user-readable "close any programs using files
in
" hint.
Refactored the function to use a $didUpdate flag instead of my earlier
draft's early `return` — that was skipping the submodule init block at
the bottom of the function. Both the update and fresh-clone paths now
fall through to the submodule init step, which is correct (git pull
doesn't auto-update submodules).
PowerShell structural check: 21 functions defined, braces balanced.
---
scripts/install.ps1 | 74 ++++++++++++++++++++++++++++++++++-----------
1 file changed, 57 insertions(+), 17 deletions(-)
diff --git a/scripts/install.ps1 b/scripts/install.ps1
index b04efd954b..dfffb9f82f 100644
--- a/scripts/install.ps1
+++ b/scripts/install.ps1
@@ -605,21 +605,61 @@ function Install-SystemPackages {
function Install-Repository {
Write-Info "Installing to $InstallDir..."
-
+
+ $didUpdate = $false
+
if (Test-Path $InstallDir) {
+ # Test-Path "$InstallDir\.git" returns True when .git is a file OR a
+ # directory OR a symlink OR a submodule-style gitfile — and also when
+ # it's a broken stub left over from a failed previous install (e.g.
+ # a partial Remove-Item that couldn't delete a locked index.lock).
+ # Validate the repo properly by asking git itself. If git can't see
+ # it as a real repo, fall through to the clone-or-zip path.
+ $repoValid = $false
if (Test-Path "$InstallDir\.git") {
+ Push-Location $InstallDir
+ try {
+ $null = git -c windows.appendAtomically=false rev-parse --is-inside-work-tree 2>$null
+ if ($LASTEXITCODE -eq 0) {
+ $repoValid = $true
+ }
+ } catch {}
+ Pop-Location
+ }
+
+ if ($repoValid) {
Write-Info "Existing installation found, updating..."
Push-Location $InstallDir
- git -c windows.appendAtomically=false fetch origin
- git -c windows.appendAtomically=false checkout $Branch
- git -c windows.appendAtomically=false pull origin $Branch
- Pop-Location
+ try {
+ git -c windows.appendAtomically=false fetch origin
+ if ($LASTEXITCODE -ne 0) { throw "git fetch failed (exit $LASTEXITCODE)" }
+ git -c windows.appendAtomically=false checkout $Branch
+ if ($LASTEXITCODE -ne 0) { throw "git checkout $Branch failed (exit $LASTEXITCODE)" }
+ git -c windows.appendAtomically=false pull origin $Branch
+ if ($LASTEXITCODE -ne 0) { throw "git pull failed (exit $LASTEXITCODE)" }
+ } finally {
+ Pop-Location
+ }
+ $didUpdate = $true
} else {
- Write-Err "Directory exists but is not a git repository: $InstallDir"
- Write-Info "Remove it or choose a different directory with -InstallDir"
- throw "Directory exists but is not a git repository: $InstallDir"
+ # Directory exists but isn't a usable git repo. Wipe it and
+ # fall through to a fresh clone. A leftover ``.git`` stub from
+ # a partial uninstall used to lock the installer into the
+ # "update" branch forever, emitting three ``fatal: not a git
+ # repository`` errors and failing with "not in a git directory".
+ Write-Warn "Existing directory at $InstallDir is not a valid git repo — replacing it."
+ try {
+ Remove-Item -Recurse -Force $InstallDir -ErrorAction Stop
+ } catch {
+ Write-Err "Could not remove $InstallDir : $_"
+ Write-Info "Close any programs that might be using files in $InstallDir (editors,"
+ Write-Info "terminals, running hermes processes) and try again."
+ throw
+ }
}
- } else {
+ }
+
+ if (-not $didUpdate) {
$cloneSuccess = $false
# Fix Windows git "copy-fd: write returned: Invalid argument" error.
@@ -640,7 +680,7 @@ function Install-Repository {
if ($LASTEXITCODE -eq 0) { $cloneSuccess = $true }
} catch { }
$env:GIT_SSH_COMMAND = $null
-
+
if (-not $cloneSuccess) {
if (Test-Path $InstallDir) { Remove-Item -Recurse -Force $InstallDir -ErrorAction SilentlyContinue }
Write-Info "SSH failed, trying HTTPS..."
@@ -658,18 +698,18 @@ function Install-Repository {
$zipUrl = "https://github.com/NousResearch/hermes-agent/archive/refs/heads/$Branch.zip"
$zipPath = "$env:TEMP\hermes-agent-$Branch.zip"
$extractPath = "$env:TEMP\hermes-agent-extract"
-
+
Invoke-WebRequest -Uri $zipUrl -OutFile $zipPath -UseBasicParsing
if (Test-Path $extractPath) { Remove-Item -Recurse -Force $extractPath }
Expand-Archive -Path $zipPath -DestinationPath $extractPath -Force
-
+
# GitHub ZIPs extract to repo-branch/ subdirectory
$extractedDir = Get-ChildItem $extractPath -Directory | Select-Object -First 1
if ($extractedDir) {
New-Item -ItemType Directory -Force -Path (Split-Path $InstallDir) -ErrorAction SilentlyContinue | Out-Null
Move-Item $extractedDir.FullName $InstallDir -Force
Write-Success "Downloaded and extracted"
-
+
# Initialize git repo so updates work later
Push-Location $InstallDir
git -c windows.appendAtomically=false init 2>$null
@@ -677,10 +717,10 @@ function Install-Repository {
git remote add origin $RepoUrlHttps 2>$null
Pop-Location
Write-Success "Git repo initialized for future updates"
-
+
$cloneSuccess = $true
}
-
+
# Cleanup temp files
Remove-Item -Force $zipPath -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force $extractPath -ErrorAction SilentlyContinue
@@ -693,7 +733,7 @@ function Install-Repository {
throw "Failed to download repository (tried git clone SSH, HTTPS, and ZIP)"
}
}
-
+
# Set per-repo config (harmless if it fails)
Push-Location $InstallDir
git -c windows.appendAtomically=false config windows.appendAtomically false 2>$null
@@ -707,7 +747,7 @@ function Install-Repository {
Write-Success "Submodules ready"
}
Pop-Location
-
+
Write-Success "Repository ready"
}