From 4766b3cdb9d0acaac9ca3c10322bf6149fd49be4 Mon Sep 17 00:00:00 2001 From: teknium1 Date: Mon, 2 Mar 2026 22:53:28 -0800 Subject: [PATCH] fix: fall back to ZIP download when git clone fails on Windows Git for Windows can completely fail to write files during clone due to antivirus software, Windows Defender Controlled Folder Access, or NTFS filter drivers. Even with windows.appendAtomically=false, the checkout phase fails with 'unable to create file: Invalid argument'. New install strategy (3 attempts): 1. git clone with -c windows.appendAtomically=false (SSH then HTTPS) 2. If clone fails: download GitHub ZIP archive, extract with Expand-Archive (Windows native, no git file I/O), then git init the result for future updates 3. All git commands now use -c flag to inject the atomic write fix Also passes -c flag on update path (fetch/checkout/pull) and makes submodule init failure non-fatal with a warning. --- scripts/install.ps1 | 106 ++++++++++++++++++++++++++------------------ 1 file changed, 63 insertions(+), 43 deletions(-) diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 00ba2972f..381d3a50e 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -416,9 +416,9 @@ function Install-Repository { if (Test-Path "$InstallDir\.git") { Write-Info "Existing installation found, updating..." Push-Location $InstallDir - git fetch origin - git checkout $Branch - git pull origin $Branch + git -c windows.appendAtomically=false fetch origin + git -c windows.appendAtomically=false checkout $Branch + git -c windows.appendAtomically=false pull origin $Branch Pop-Location } else { Write-Err "Directory exists but is not a git repository: $InstallDir" @@ -426,73 +426,93 @@ function Install-Repository { throw "Directory exists but is not a git repository: $InstallDir" } } else { + $cloneSuccess = $false + # Fix Windows git "copy-fd: write returned: Invalid argument" error. # Git for Windows can fail on atomic file operations (hook templates, # config lock files) due to antivirus, OneDrive, or NTFS filter drivers. - # Setting windows.appendAtomically=false via ENVIRONMENT bypasses the - # issue entirely — git reads these before touching any files, unlike - # --global config which itself may fail to write. + # The -c flag injects config before any file I/O occurs. Write-Info "Configuring git for Windows compatibility..." $env:GIT_CONFIG_COUNT = "1" $env:GIT_CONFIG_KEY_0 = "windows.appendAtomically" $env:GIT_CONFIG_VALUE_0 = "false" - # Also try global config (may fail but harmless) git config --global windows.appendAtomically false 2>$null - # Try SSH first (for private repo access), fall back to HTTPS. - # GIT_SSH_COMMAND with BatchMode=yes prevents SSH from hanging - # when no key is configured (fails immediately instead of prompting). - # - # IMPORTANT: Do NOT use 2>&1 on git commands in PowerShell. - # With $ErrorActionPreference = "Stop", PowerShell wraps captured - # stderr lines in ErrorRecord objects, turning git's normal progress - # messages ("Cloning into ...") into terminating NativeCommandErrors. - # Let stderr flow to the console naturally (like OpenClaw does). + # Try SSH first, then HTTPS, with -c flag for atomic write fix Write-Info "Trying SSH clone..." $env:GIT_SSH_COMMAND = "ssh -o BatchMode=yes -o ConnectTimeout=5" try { - git clone --branch $Branch --recurse-submodules $RepoUrlSsh $InstallDir - $sshExitCode = $LASTEXITCODE - } catch { - $sshExitCode = 1 - } + git -c windows.appendAtomically=false clone --branch $Branch --recurse-submodules $RepoUrlSsh $InstallDir + if ($LASTEXITCODE -eq 0) { $cloneSuccess = $true } + } catch { } $env:GIT_SSH_COMMAND = $null - if ($sshExitCode -eq 0) { - Write-Success "Cloned via SSH" - } else { - # Clean up partial SSH clone before retrying + if (-not $cloneSuccess) { if (Test-Path $InstallDir) { Remove-Item -Recurse -Force $InstallDir -ErrorAction SilentlyContinue } Write-Info "SSH failed, trying HTTPS..." - git clone --branch $Branch --recurse-submodules $RepoUrlHttps $InstallDir - - if ($LASTEXITCODE -eq 0) { - Write-Success "Cloned via HTTPS" - } else { - # Last resort: skip hook templates entirely (they're optional sample files) - if (Test-Path $InstallDir) { Remove-Item -Recurse -Force $InstallDir -ErrorAction SilentlyContinue } - Write-Warn "Standard clone failed, retrying without hook templates..." - git clone --branch $Branch --recurse-submodules --template="" $RepoUrlHttps $InstallDir + try { + git -c windows.appendAtomically=false clone --branch $Branch --recurse-submodules $RepoUrlHttps $InstallDir + if ($LASTEXITCODE -eq 0) { $cloneSuccess = $true } + } catch { } + } + + # Fallback: download ZIP archive (bypasses git file I/O issues entirely) + if (-not $cloneSuccess) { + if (Test-Path $InstallDir) { Remove-Item -Recurse -Force $InstallDir -ErrorAction SilentlyContinue } + Write-Warn "Git clone failed — downloading ZIP archive instead..." + try { + $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" - if ($LASTEXITCODE -eq 0) { - Write-Success "Cloned via HTTPS (no templates)" - } else { - Write-Err "Failed to clone repository" - throw "Failed to clone repository" + 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 + git -c windows.appendAtomically=false config windows.appendAtomically false 2>$null + 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 + } catch { + Write-Err "ZIP download also failed: $_" } } + + if (-not $cloneSuccess) { + throw "Failed to download repository (tried git clone SSH, HTTPS, and ZIP)" + } } - # Also set per-repo (in case global wasn't persisted) + # Set per-repo config (harmless if it fails) Push-Location $InstallDir - git config windows.appendAtomically false + git -c windows.appendAtomically=false config windows.appendAtomically false 2>$null # Ensure submodules are initialized and updated Write-Info "Initializing submodules (mini-swe-agent, tinker-atropos)..." - git submodule update --init --recursive + git -c windows.appendAtomically=false submodule update --init --recursive 2>$null + if ($LASTEXITCODE -ne 0) { + Write-Warn "Submodule init failed (terminal/RL tools may need manual setup)" + } else { + Write-Success "Submodules ready" + } Pop-Location - Write-Success "Submodules ready" Write-Success "Repository ready" }