Windows in-app update silently fails when the installer signature check exceeds electron-updater's 20s PowerShell timeout#20345

opened20d agobyCcausercode

What happened

On a corporate-managed Windows 11 machine, the in-app update never completes. Clicking download shows progress up to 100%, then the update quietly goes nowhere. No visible error appears. The same nightly updates fine on a personal Windows machine.

Diagnosis

The download itself succeeds. The failure is electron-updater's Windows signature check, which runs after the download and before the temp file is renamed into place.

  • NsisUpdater calls verifySignature() from windowsExecutableCodeSignatureVerifier.js. That function spawns powershell.exe ... Get-AuthenticodeSignature -LiteralPath '<temp file>' | ConvertTo-Json -Compress through execFile with a hardcoded timeout: 20 * 1000.
  • On machines with endpoint security that scans a newly written ~240 MB executable on first open (and/or inspects PowerShell launches), that first Get-AuthenticodeSignature call can take longer than 20 s. Node kills the child, and the rejection is Error: Command failed: <command> with empty stderr.
  • The failure was logged 27.1 s after download progress: 100. That gap is consistent with SHA-512 verification plus the 20 s kill.
  • After the failure, I re-ran the exact same command by hand against the same file. It completed in 4.2 s with Status: 0 (Valid) and signer CN=T3 Tools Inc. So the file and signature are fine; only the first, scan-blocked invocation is slow. PowerShell runs in FullLanguage mode on this machine, so the check is not blocked by policy.
  • The leftover pending/temp-T3-Code-0.1.1-nightly.20261016.3218-x64.exe is exactly 242,348,152 bytes, and its SHA-512 matches nightly.yml for that release.

T3 Code does not override verifyUpdateCodeSignature (apps/desktop/src/electron/ElectronUpdater.ts), so it inherits the 20 s limit. describeUpdateErrorCause keeps only message and string code. That drops killed: true / signal: "SIGTERM", so the trace can't tell a timeout apart from a real signature failure.

Steps to reproduce

Depends on the endpoint-security stack, so it is not deterministic everywhere:

  1. On a Windows machine with real-time/EDR scanning of newly written executables under %LOCALAPPDATA%, run a nightly build.
  2. Open the update flyout and download the available update.
  3. Progress reaches 100%, then the update does not become ready to install. desktop.trace.ndjson shows ElectronUpdaterDownloadUpdateError with the Get-AuthenticodeSignature command as the cause.
  4. %LOCALAPPDATA%\t3code-updater\pending\ keeps a full-size temp-*.exe that never gets promoted.

Without that kind of AV, a unit test can stand in: make the injected verifier reject the way execFile does on timeout (killed: true, empty stderr).

Version

0.0.46-nightly.20261015.3145, updating to 0.1.1-nightly.20261016.3218. electron-updater ^6.8.9 per apps/desktop/package.json on main.

Environment

Windows 11 Enterprise 10.0.26100 x64, corporate-managed (endpoint security, no admin rights), desktop app per-user NSIS install, nightly channel.

Evidence

shell
1# desktop.trace.ndjson (home path redacted)
2desktop.updates.applyDownloadProgress download progress {"percent":100} t=…666.31s
3desktop.updates.handleUpdaterError "Desktop updater download operation reported an error." t=…693.45s (+27.1s)
4 errorTag: DesktopUpdaterReportedError, operation: download
5 cause: Error: Command failed: set "PSModulePath=" & chcp 65001 >NUL & powershell.exe -NoProfile -NonInteractive -InputFormat None -Command "Get-AuthenticodeSignature -LiteralPath '~\AppData\Local\t3code-updater\pending\temp-T3-Code-0.1.1-nightly.20261016.3218-x64.exe' | ConvertTo-Json -Compress"
6desktop.updates.handleDownloadFailure "Electron updater failed to download the update on channel nightly."
7 errorTag: ElectronUpdaterDownloadUpdateError (same cause, no stderr)
8
9# Same command re-run manually minutes later against the same file:
10exit=0 elapsed=4.2s "Status":0 signer CN=T3 Tools Inc
11
12# Leftover file vs. published nightly.yml
13pending/temp-T3-Code-0.1.1-nightly.20261016.3218-x64.exe size=242348152 sha512 matches nightly.yml
  • #6035: same failed to download the update string on Windows, but it failed in ~200 ms (before any download), so it's a different cause.
  • #11708: same string, but the reporter traced it to a failing SSD.
  • #4812: same string, but on macOS behind a TLS-inspecting proxy, stalling at 0%.

I found no existing electron-builder issue about this timeout.

Fix applied or workaround

Nothing changed on the machine. Workaround: close T3 Code and run the leftover pending\temp-*.exe (after checking its SHA-512 against nightly.yml), or download the installer from the release page. Neither needs admin rights.

Suggested fixes

NsisUpdater already exposes a setter for this check: autoUpdater.verifyUpdateCodeSignature = (publisherNames, path) => Promise<string | null>. electron-updater's own docs suggest it for custom verification. Options, smallest first:

  1. Retry the default verifier on timeout (Windows only). Capture the original autoUpdater.verifyUpdateCodeSignature, wrap it, and retry once or twice when the rejection is a killed child process (error.killed === true). The second run is fast because the AV scan of the file is already done and cached (4.2 s here). Publisher matching and the security posture stay exactly the same. The logic is a few lines in ElectronUpdater.ts, plus a unit test for timeout → retry → success, and for a real mismatch still failing.
  2. Avoid spawning PowerShell entirely. Verify with WinVerifyTrust in-process, for example via win-verify-trust, which the electron-updater docs mention. This removes the 20 s limit, the PowerShell process launch that endpoint security tends to inspect, and the PSModulePath/chcp workarounds.
  3. Make timeouts diagnosable. Have describeUpdateErrorCause include killed/signal (and numeric exit codes) for child-process errors, or tag signature-verification failures distinctly, so Command failed: with empty stderr shows up as a timeout.
  4. Surface the failure. The user saw no error. A download failure with a short hint ("Signature verification timed out — security software may be scanning the installer. Retry?") would make this self-service.

A configurable timeout in electron-updater itself would also help, but options 1 or 2 are fully within T3 Code's control.

Filed by

Claude Code (claude-opus-5-5), investigating by hand following .github/triage/PLAYBOOK.md, not via npx t3 triage.

Synced from GitHub

This was investigated following .github/triage/PLAYBOOK.md and structured per the via-triage template, but the via-triage label was dropped on creation since I don't have permission to apply labels. Could a maintainer add it? Thanks!

Note

Grok responding on behalf of Julius.

Accepting this as a bug in how T3 handles electron-updater's Windows signature check. The 20 s timeout is electron-updater's, but T3 can guard against it.

What main does today:

  • apps/desktop/src/electron/ElectronUpdater.ts never overrides autoUpdater.verifyUpdateCodeSignature, so a download inherits electron-updater's built-in PowerShell Get-AuthenticodeSignature check and its time limit (electron-updater is ^6.8.9 in apps/desktop/package.json).
  • describeUpdateErrorCause in apps/desktop/src/updates/DesktopUpdates.ts keeps only the error's tag, string code and the first line of message. It drops killed and signal, so in the trace a timed-out check looks the same as a genuine signature failure.

Workaround for now: close T3 Code and download the installer from the releases page. Or, after checking the leftover pending\temp-*.exe against the SHA-512 in the release's .yml, run that file.

The fix direction (retrying the check after a timeout or verifying in-process, and reporting timeouts clearly) is still open.

Synced from GitHub

Answers to the triage questions (the triage comment appears to have been removed, but it reached my inbox), plus a test of the reverted retry and uncapped timings.

Current nightly: yes. Reproduced on 2026-10-22 updating 0.1.1-nightly.20261020.3411 (d36b7da, after revert 8fc72da) → 0.1.2-nightly.20261022.3512. No error is shown in the app; the update just never becomes ready. Every update since filing has needed a manual install.

107:45:50.0 download progress 100% (243,760,776 bytes)
207:45:50.5 powershell.exe Get-AuthenticodeSignature … temp-T3-Code-0.1.2-nightly.20261022.3512-x64.exe starts
307:46:18.2 handleUpdaterError + handleDownloadFailure: "Command failed: … Get-AuthenticodeSignature …" (no stderr)
407:46:44 that powershell.exe exits on its own: ~53.5 s wall, ~4.4 s CPU

Retry test (#21273). 0.1.1-nightly.20261020.3401 is the only published build with the reverted retry. Installed it and updated to 3512 from it:

108:04:15.1 download progress 100%
208:04:15.4 check 1 powershell starts
308:04:37.4 check 2 (retry) starts; check 1 still running
408:05:04.2 handleDownloadFailure (same "Command failed" cause); a failure popup was shown on this build
508:05:06.4 check 1 exits: ~50.9 s wall, ~1.9 s CPU
608:05:16.0 check 2 exits: ~38.6 s wall, ~2.6 s CPU

The retry fires, but one immediate retry does not help here. The 20 s timeout kills cmd.exe (shell: true), not powershell.exe, so the retry runs while the first check is still blocked, and it times out too.

Uncapped check timing. To check whether a longer limit would pass, I downloaded fresh copies of the 3512 installer three times and immediately ran the identical cmd.exe … powershell.exe Get-AuthenticodeSignature … | ConvertTo-Json -Compress command with no timeout:

TrialCheck durationResult
155.8 sStatus 0, CN=T3 Tools Inc, no stderr
256.0 ssame
354.4 ssame

Across all five observations on this machine (two updater attempts, three uncapped trials), the first check on a freshly downloaded installer takes 51–56 s and then succeeds. Once the file has been scanned, the same command takes ~3.6 s. These copies were downloaded by PowerShell into a folder next to the updater cache rather than by the app itself, but the timings match the updater runs. I don't know how this varies on other machines or security products, so I'm not suggesting a specific limit. On this machine, anything under ~60 s fails.

Security product: A third-party enterprise endpoint-security suite with on-access scanning. Windows Defender is registered but passive.

Scan/block event: not available. This is a managed machine without admin rights, the product's current logs aren't readable, and the Defender log has nothing in that window. What separates a timeout from another PowerShell failure:

  • Every check stays alive past 20 s and exits on its own.
  • Each uses ~2–4 s of CPU over 38–56 s, so it is mostly waiting.
  • With no timeout, the same command returns a valid signature.

Happy to test a candidate build on this machine.