Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 82 additions & 24 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,47 +34,105 @@ jobs:
- name: Build
run: yarn build

- name: Copy config and dist
- name: Sync live tree
shell: powershell
run: |
Copy-Item -Path "config.json" -Destination "C:\game-ci\help-bot\config.json" -Force
Copy-Item -Path "dist\*" -Destination "C:\game-ci\help-bot\dist\" -Recurse -Force
$ErrorActionPreference = 'Stop'
$deployRoot = 'C:\game-ci\help-bot'

- name: Restart help-bot (kill node so NSSM auto-restarts with new code)
New-Item -ItemType Directory -Force -Path $deployRoot | Out-Null
New-Item -ItemType Directory -Force -Path (Join-Path $deployRoot 'dist') | Out-Null
New-Item -ItemType Directory -Force -Path (Join-Path $deployRoot 'src') | Out-Null

robocopy "src" "$deployRoot\src" /MIR /R:2 /W:2 /NFL /NDL /NJH /NJS /NP
if ($LASTEXITCODE -gt 7) { throw "robocopy src failed with exit code $LASTEXITCODE" }

robocopy "dist" "$deployRoot\dist" /MIR /R:2 /W:2 /NFL /NDL /NJH /NJS /NP
if ($LASTEXITCODE -gt 7) { throw "robocopy dist failed with exit code $LASTEXITCODE" }

foreach ($file in @('package.json', 'yarn.lock', 'tsconfig.json', 'config.json', 'startup.ps1')) {
Copy-Item -Path $file -Destination (Join-Path $deployRoot $file) -Force
}

- name: Repair live install
shell: powershell
run: |
# Get the NSSM child PID for our service
$nssmPid = (Get-WmiObject Win32_Service -Filter "Name='gameci-help-bot'").ProcessId
if ($nssmPid -and $nssmPid -ne 0) {
# Find child node.exe processes of the NSSM wrapper
$children = Get-WmiObject Win32_Process | Where-Object { $_.ParentProcessId -eq $nssmPid }
foreach ($child in $children) {
Write-Host "Killing child process $($child.ProcessId) ($($child.Name))"
Stop-Process -Id $child.ProcessId -Force -ErrorAction SilentlyContinue
}
# NSSM will auto-restart the child process
Start-Sleep -Seconds 3
$ErrorActionPreference = 'Stop'
$deployRoot = 'C:\game-ci\help-bot'
Push-Location $deployRoot
try {
yarn install --frozen-lockfile
yarn build
Comment on lines +60 to +65
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In Windows PowerShell 5.1, do native executables like yarnandnssmthrow on non-zero exit codes when$ErrorActionPreference = 'Stop'is set, or must scripts check$LASTEXITCODE explicitly?

💡 Result:

In Windows PowerShell 5.1, native executables like yarn and nssm do not throw terminating errors on non-zero exit codes when $ErrorActionPreference = 'Stop' is set. Scripts must check $LASTEXITCODE explicitly (or use $?) after calling such executables to detect and handle failures. This behavior is consistent across multiple authoritative sources discussing PowerShell 5.1 error handling. $ErrorActionPreference = 'Stop' makes non-terminating errors from PowerShell cmdlets into terminating errors catchable by try/catch, but external native commands (executables) only set $LASTEXITCODE to the process exit code and $? to $false if non-zero; they do not generate PowerShell errors or respect ErrorActionPreference for exit codes. Official docs (about_Preference_Variables) demonstrate the preference working for PowerShell commands like Write-Error, but not external exes. GitHub issue #3996 confirms no terminating error from native non-zero exit even with 'Stop', requiring explicit $LASTEXITCODE checks. Stack Overflow answers (e.g., on call operator, bash set -e equivalent) explicitly state the need to check $LASTEXITCODE -ne 0 and throw manually. This changed in PowerShell 7.4+ with $PSNativeCommandUseErrorActionPreference = $true, but not in 5.1. Example: $ErrorActionPreference = 'Stop' yarn --version # Assume non-zero exit if ($LASTEXITCODE -ne 0) { throw "yarn failed with exit code $LASTEXITCODE" } Write-Host "Continues here only if success" Without the if check, execution continues despite failure.

Citations:


🏁 Script executed:

# Check if file exists and get its structure
ls -la .github/workflows/deploy.yml
wc -l .github/workflows/deploy.yml

Repository: game-ci/help-bot

Length of output: 170


🏁 Script executed:

# Read the specific line ranges mentioned in the review
echo "=== Lines 60-65 (yarn install/build) ==="
sed -n '55,70p' .github/workflows/deploy.yml

echo ""
echo "=== Lines 82-102 (NSSM calls) ==="
sed -n '78,105p' .github/workflows/deploy.yml

echo ""
echo "=== Lines 108-112 (nssm restart) ==="
sed -n '105,115p' .github/workflows/deploy.yml

echo ""
echo "=== Lines 118-124 (error handling) ==="
sed -n '115,130p' .github/workflows/deploy.yml

Repository: game-ci/help-bot

Length of output: 2924


Native command failures won't fail these steps or trigger the fallback.

These yarn and nssm invocations run under shell: powershell, so non-zero exit codes do not throw. That means the live-repair step can continue after a failed install/build, the NSSM reconciliation can partially fail silently, and the nssm restart catch block never runs.

Suggested pattern
       - name: Repair live install
         shell: powershell
         run: |
           $ErrorActionPreference = 'Stop'
           $deployRoot = 'C:\game-ci\help-bot'
           Push-Location $deployRoot
           try {
             yarn install --frozen-lockfile
+            if ($LASTEXITCODE -ne 0) { throw "yarn install failed with exit code $LASTEXITCODE" }
             yarn build
+            if ($LASTEXITCODE -ne 0) { throw "yarn build failed with exit code $LASTEXITCODE" }
           } finally {
             Pop-Location
           }
…
-          try {
-            nssm restart gameci-help-bot
-          } catch {
-            Write-Host "NSSM restart failed, trying service start: $_"
+          nssm restart gameci-help-bot
+          if ($LASTEXITCODE -ne 0) {
+            Write-Host "NSSM restart failed, trying service start"
             Start-Service -Name 'gameci-help-bot'
           }

Apply the same $LASTEXITCODE guard after each nssm install, nssm stop, nssm set, and nssm get call.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$ErrorActionPreference = 'Stop'
$deployRoot = 'C:\game-ci\help-bot'
Push-Location $deployRoot
try {
yarn install --frozen-lockfile
yarn build
$ErrorActionPreference = 'Stop'
$deployRoot = 'C:\game-ci\help-bot'
Push-Location $deployRoot
try {
yarn install --frozen-lockfile
if ($LASTEXITCODE -ne 0) { throw "yarn install failed with exit code $LASTEXITCODE" }
yarn build
if ($LASTEXITCODE -ne 0) { throw "yarn build failed with exit code $LASTEXITCODE" }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/deploy.yml around lines 60 - 65, The PowerShell steps run
native commands (yarn, nssm) that don't auto-throw on non-zero exit; after each
native invocation—specifically after the yarn install and yarn build calls and
after every nssm install, nssm stop, nssm set and nssm get invocation—check
$LASTEXITCODE and abort/throw (e.g., throw or exit 1) if non-zero so the
try/catch and fallback logic triggers; update the block around Push-Location and
the try/catch to add these $LASTEXITCODE guards immediately after each command.

} finally {
Pop-Location
}

- name: Reconcile NSSM service
shell: powershell
run: |
$ErrorActionPreference = 'Stop'
$serviceName = 'gameci-help-bot'
$deployRoot = 'C:\game-ci\help-bot'
$startupScript = Join-Path $deployRoot 'startup.ps1'
$powershellPath = (Get-Command powershell).Source
$logFile = Join-Path $deployRoot 'logs\service.log'

New-Item -ItemType Directory -Force -Path (Join-Path $deployRoot 'logs') | Out-Null

$service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue
if (-not $service) {
Write-Host "Installing missing NSSM service"
nssm install $serviceName $powershellPath "-ExecutionPolicy" "Bypass" "-File" $startupScript
} else {
Write-Host "Service not running or PID not found, attempting service start"
try { Start-Service -Name 'gameci-help-bot' -ErrorAction Stop } catch {
Write-Host "Cannot start service: $_"
Write-Host "Reconfiguring existing NSSM service"
try {
nssm stop $serviceName
} catch {
Write-Host "Service stop before reconfigure returned: $_"
}
}

nssm set $serviceName Application $powershellPath
nssm set $serviceName AppParameters "-ExecutionPolicy Bypass -File `"$startupScript`""
nssm set $serviceName AppDirectory $deployRoot
nssm set $serviceName AppStdout $logFile
nssm set $serviceName AppStderr $logFile
nssm set $serviceName AppStdoutCreationDisposition 1
nssm set $serviceName AppStderrCreationDisposition 1
nssm set $serviceName Start SERVICE_AUTO_START

- name: Restart help-bot
shell: powershell
run: |
$ErrorActionPreference = 'Stop'
try {
nssm restart gameci-help-bot
} catch {
Write-Host "NSSM restart failed, trying service start: $_"
Start-Service -Name 'gameci-help-bot'
}

- name: Verify service is running
shell: powershell
run: |
$ErrorActionPreference = 'Stop'
Start-Sleep -Seconds 8
$svc = Get-Service -Name 'gameci-help-bot' -ErrorAction SilentlyContinue
if ($svc.Status -ne 'Running') {
Write-Error "Service is not running: $($svc.Status)"
Get-Content "C:\game-ci\help-bot\logs\service.log" -Tail 30
exit 1
Comment on lines +118 to 124
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

With $ErrorActionPreference = 'Stop', does Write-Error terminate a Windows PowerShell script immediately, or do subsequent statements still run?

💡 Result:

With $ErrorActionPreference = 'Stop', Write-Error terminates a Windows PowerShell script immediately, and subsequent statements do not run.

Citations:


The log tail is unreachable on the failure path.

With $ErrorActionPreference = 'Stop', Write-Error on line 122 terminates execution immediately and prevents lines 123-124 from running. This loses the service log output exactly when you need it for troubleshooting.

Proposed fix
-          if ($svc.Status -ne 'Running') {
-            Write-Error "Service is not running: $($svc.Status)"
-            Get-Content "C:\game-ci\help-bot\logs\service.log" -Tail 30
-            exit 1
-          }
+          if (-not $svc -or $svc.Status -ne 'Running') {
+            $status = if ($svc) { $svc.Status } else { 'Missing' }
+            Write-Host "Service is not running: $status"
+            if (Test-Path "C:\game-ci\help-bot\logs\service.log") {
+              Get-Content "C:\game-ci\help-bot\logs\service.log" -Tail 30
+            }
+            throw "gameci-help-bot failed verification"
+          }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$ErrorActionPreference = 'Stop'
Start-Sleep -Seconds 8
$svc = Get-Service -Name 'gameci-help-bot' -ErrorAction SilentlyContinue
if ($svc.Status -ne 'Running') {
Write-Error "Service is not running: $($svc.Status)"
Get-Content "C:\game-ci\help-bot\logs\service.log" -Tail 30
exit 1
$ErrorActionPreference = 'Stop'
Start-Sleep -Seconds 8
$svc = Get-Service -Name 'gameci-help-bot' -ErrorAction SilentlyContinue
if (-not $svc -or $svc.Status -ne 'Running') {
$status = if ($svc) { $svc.Status } else { 'Missing' }
Write-Host "Service is not running: $status"
if (Test-Path "C:\game-ci\help-bot\logs\service.log") {
Get-Content "C:\game-ci\help-bot\logs\service.log" -Tail 30
}
throw "gameci-help-bot failed verification"
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/deploy.yml around lines 118 - 124, The Write-Error call is
triggering the Stop preference and preventing the subsequent Get-Content tail
and exit from running; update the failure path in the service check (the block
that inspects $svc and its .Status for the 'gameci-help-bot' service) so it logs
the error without terminating early (for example replace Write-Error with
Write-Host/Write-Output or call Write-Error with -ErrorAction SilentlyContinue),
ensure you null-check $svc before accessing $svc.Status, then run Get-Content
"C:\game-ci\help-bot\logs\service.log" -Tail 30 and finally exit 1 so the logs
are always emitted on failure.

}
# Verify the node process restarted recently
$nodes = Get-Process node -ErrorAction SilentlyContinue | Where-Object { $_.StartTime -gt (Get-Date).AddMinutes(-2) }
if ($nodes) {
Write-Host "Service is running with fresh node process (PID $($nodes[0].Id), started $($nodes[0].StartTime))"
} else {
Write-Host "WARNING: Service is running but node process may not have restarted"
$app = nssm get gameci-help-bot Application
$args = nssm get gameci-help-bot AppParameters
$dir = nssm get gameci-help-bot AppDirectory
if ($app -notmatch 'powershell(\.exe)?$') {
Write-Error "Service Application is not PowerShell: $app"
}
if ($args -notmatch 'startup\.ps1') {
Write-Error "Service AppParameters do not point at startup.ps1: $args"
}
if ($dir.Trim() -ne 'C:\game-ci\help-bot') {
Write-Error "Service AppDirectory is unexpected: $dir"
}
Write-Host "Service is running with reconciled NSSM configuration"
53 changes: 26 additions & 27 deletions startup.ps1
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# startup.ps1 — Auto-update wrapper for NSSM service
# Runs on every service start (including reboot): pulls latest, builds, then starts the bot.
# startup.ps1 — NSSM startup wrapper for deployed help-bot
# Starts from the deployed working tree and performs a minimal repair if runtime artifacts are missing.
$ErrorActionPreference = "Continue"
$repoDir = "C:\game-ci\help-bot"
$logFile = "$repoDir\logs\startup.log"
Expand All @@ -14,31 +14,30 @@ New-Item -ItemType Directory -Force -Path "$repoDir\logs" | Out-Null

Log "=== Service starting ==="

# Step 1: git pull
try {
Log "Pulling latest from main..."
$pullOutput = git pull origin main 2>&1 | Out-String
Log " git pull: $($pullOutput.Trim())"
} catch {
Log " git pull failed: $_ (continuing with current version)"
}

# Step 2: npm ci
try {
Log "Installing dependencies..."
npm ci 2>&1 | Out-Null
Log " npm ci: done"
} catch {
Log " npm ci failed: $_ (continuing with current modules)"
}

# Step 3: Build
try {
Log "Building..."
npm run build 2>&1 | Out-Null
Log " Build: done"
} catch {
Log " Build failed: $_ (continuing with existing dist)"
$distCli = Join-Path $repoDir "dist\cli.js"
$nodeModulesDir = Join-Path $repoDir "node_modules"
$needsRepair = -not (Test-Path $distCli) -or -not (Test-Path $nodeModulesDir)

if ($needsRepair) {
Log "Runtime artifacts missing; repairing local install..."

try {
Log "Installing dependencies..."
yarn install --frozen-lockfile 2>&1 | Out-Null
Log " yarn install: done"
} catch {
Log " yarn install failed: $_"
}

try {
Log "Building..."
yarn build 2>&1 | Out-Null
Log " Build: done"
} catch {
Log " Build failed: $_"
Comment on lines +24 to +37
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In Windows PowerShell 5.1, do non-zero exit codes from native commands like yarntriggertry/catchor obey$ErrorActionPreference? What is the recommended pattern for logging or failing based on $LASTEXITCODE?

💡 Result:

In Windows PowerShell 5.1, non-zero exit codes from native commands like yarn do not trigger try/catch blocks and are not affected by $ErrorActionPreference. These mechanisms handle PowerShell-native errors (terminating or non-terminating with -ErrorAction Stop), but external executables only set $LASTEXITCODE and $? ($false for non-zero). You must explicitly check $LASTEXITCODE after the command: yarn install if ($LASTEXITCODE -ne 0) { Write-Error "yarn failed with exit code $LASTEXITCODE" # Log details, fail script, etc. } Recommended pattern for logging/failing: $output = yarn install 2>&1 if ($LASTEXITCODE -ne 0) { Write-Warning "yarn failed (exit code $LASTEXITCODE): $output" exit $LASTEXITCODE # Propagate exit code } Or wrap in a reusable function: function Invoke-NativeCommand { param([scriptblock] $Command, [string] $ErrorMessage = "Command failed with exit code $LASTEXITCODE") & $Command if ($LASTEXITCODE -ne 0) { throw $ErrorMessage } } Invoke-NativeCommand { yarn install } "yarn install failed" This catches the failure in try/catch if needed, logs via $Error or custom output, and allows explicit failing with exit $LASTEXITCODE. Note: Redirect stderr (2>&1) to capture output; without it, stderr prints but doesn't affect error handling in PS 5.1.

Citations:


yarn failures are treated as success here.

In PowerShell 5.1, non-zero exit codes from native commands like yarn install or yarn build do not trigger catch blocks—they only set $LASTEXITCODE. The script will execute Log " yarn install: done" even when the command fails, allowing a broken build to proceed.

Proposed fix
     try {
         Log "Installing dependencies..."
         yarn install --frozen-lockfile 2>&1 | Out-Null
-        Log "  yarn install: done"
+        if ($LASTEXITCODE -eq 0) {
+            Log "  yarn install: done"
+        } else {
+            Log "  yarn install failed with exit code $LASTEXITCODE"
+        }
     } catch {
         Log "  yarn install failed: $_"
     }

     try {
         Log "Building..."
         yarn build 2>&1 | Out-Null
-        Log "  Build: done"
+        if ($LASTEXITCODE -eq 0) {
+            Log "  Build: done"
+        } else {
+            Log "  Build failed with exit code $LASTEXITCODE"
+        }
     } catch {
         Log "  Build failed: $_"
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
Log "Installing dependencies..."
yarn install --frozen-lockfile 2>&1 | Out-Null
Log " yarn install: done"
} catch {
Log " yarn install failed: $_"
}
try {
Log "Building..."
yarn build 2>&1 | Out-Null
Log " Build: done"
} catch {
Log " Build failed: $_"
try {
Log "Installing dependencies..."
yarn install --frozen-lockfile 2>&1 | Out-Null
if ($LASTEXITCODE -eq 0) {
Log " yarn install: done"
} else {
Log " yarn install failed with exit code $LASTEXITCODE"
}
} catch {
Log " yarn install failed: $_"
}
try {
Log "Building..."
yarn build 2>&1 | Out-Null
if ($LASTEXITCODE -eq 0) {
Log " Build: done"
} else {
Log " Build failed with exit code $LASTEXITCODE"
}
} catch {
Log " Build failed: $_"
}
🧰 Tools
🪛 PSScriptAnalyzer (1.25.0)

[warning] Missing BOM encoding for non-ASCII encoded file 'startup.ps1'

(PSUseBOMForUnicodeEncodedFile)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@startup.ps1` around lines 24 - 37, The try/catch blocks around the yarn calls
log success even when yarn fails because PowerShell 5.1 doesn't throw on native
command non-zero exits; after each yarn invocation (the yarn install and yarn
build calls around the Log(...) and "Build: done" messages) check $LASTEXITCODE
and if it's non-zero, throw or call exit with a non-zero code so the catch path
runs; update the catch handlers to Log the error and terminate (e.g., exit 1) so
failures don't proceed as successes.

}
} else {
Log "Runtime artifacts present; skipping repair build"
}

Log "Starting bot..."
Expand Down
Loading