pingdotgg / pingdotgg/t3code

[Bug]: Unconditional PowerShell profile discovery delays Windows desktop startup

Open
#4,403 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
23k
Forks
5.9k
Avg merge
11h 14m
Merged PRs (30d)
357

Description

Before submitting
  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.
Area

apps/desktop

Steps to reproduce
  1. On Windows, ensure pwsh.exe is installed. The affected machine also has Windows PowerShell 5.1 available as powershell.exe.
  2. Ensure node.exe is already available from the inherited/no-profile PATH:
    pwsh.exe -NoProfile -NonInteractive -Command "(Get-Command node).Source"
    
  3. Use a normal PowerShell profile that performs environment initialization. No artificial delay or Start-Sleep is required for this reproduction.
  4. Fully exit T3 Code and verify no prior T3 Code/Electron process remains, to keep this separate from #4395.
  5. Start the packaged desktop app normally.
  6. Inspect:
    %USERPROFILE%\.t3\userdata\logs\desktop.trace.ndjson*
    
  7. Find the desktop.shellEnvironment.installWindowsEnvironment and readWindowsEnvironment spans.
  8. Repeat several cold launches.

On the affected machine, every observed startup performs both:

  1. a -NoProfile environment read; and
  2. a profile-loaded environment read.

This happens even though node.exe is already available after the first read.

Expected behavior

Windows environment hydration should first construct a baseline environment without loading user profiles.

If that baseline is sufficient to launch the required runtime, profile discovery should be skipped. This was the behavior introduced in #1729: load the PowerShell profile only when node is not available from the baseline environment.

If profile discovery is still required—for example, to obtain FNM variables or additional profile-defined paths—the complete discovery operation should have one total startup budget. Trying another PowerShell candidate should consume the remaining budget rather than start a fresh timeout.

A profile failure or timeout should remain best-effort: preserve the baseline environment and continue startup.

Actual behavior

The affected implementation unconditionally performs the no-profile read and then the profile-loaded read:

Environment hydration also occurs before user-data resolution, lifecycle registration, the single-instance configuration, and app.whenReady().

Each read tries the following candidates sequentially:

pwsh.exe
powershell.exe

Each candidate receives a new five-second timeout. Because both no-profile and profile-loaded reads use this loop, failures can consume up to four separate timeout budgets, plus process-termination overhead.

Measured packaged-app startup traces

Twenty-three successful Windows desktop starts were measured between 2026-07-15 and 2026-07-23:

Startup phase Count Minimum Median p95 Maximum Mean
No-profile read 23 1.16 s 2.06 s 3.31 s 3.32 s 2.11 s
Profile-loaded read 23 2.21 s 4.20 s 9.70 s 11.35 s 4.34 s
Total shell hydration 23 3.54 s 6.31 s 12.21 s 13.35 s 6.45 s

Every trace contained exactly two aggregate environment reads in this order: no-profile, then profile-loaded.

Two of the 23 starts reached profile-probe timeouts:

trace 8be0a1df…
pwsh.exe profile probe:       timed out
powershell.exe profile probe: timed out
profile phase:                11,354.5 ms
total shell hydration:        13,348.0 ms

trace c03677f6…
pwsh.exe profile probe:       timed out
powershell.exe fallback:      succeeded
profile phase:                 9,700.2 ms
total shell hydration:        12,206.7 ms

Across these traces, shell hydration accounted for a median 98.5% of the main-process desktop.startup span.

These measurements cover only main-process startup through the bootstrap request. They do not measure the additional time until the renderer, backend, or environment reconnect is usable; those layers can make the user-visible wait longer.

Direct profile/no-profile comparison

Five alternating executions of the same pwsh.exe environment-capture shape used by the app produced:

-NoProfile:
  minimum:  671.6 ms
  median: 1,014.7 ms
  maximum: 1,738.5 ms

Profile loaded:
  minimum: 3,516.0 ms
  median: 3,881.5 ms
  maximum: 4,736.5 ms

For this environment:

PATH identical:              yes
node available in both:      yes
FNM_DIR present:             no
FNM_MULTISHELL_PATH present: no

The profile-loaded probe therefore added several seconds without changing any of the values collected by the app.

Suspected regression

PR #1729 intentionally implemented a conditional fallback:

  1. read PATH without loading profiles;
  2. merge inherited and known CLI paths;
  3. return immediately if node is available;
  4. load the profile only if node remains unavailable.

Its tests asserted that the environment reader was invoked only once when node was already available.

The unconditional profile read appears to have been introduced during the large Effect port in #2546. That PR does not document an intended change to the Windows profile-loading policy, so this looks like a behavioral regression, although maintainer confirmation would be useful.

One open design question is whether the unconditional read was intended to collect FNM_DIR, FNM_MULTISHELL_PATH, or the complete profile PATH even when node already exists. If so, that requirement should be explicit and the work should still be bounded or removed from the synchronous startup path.

Possible acceptance criteria
  • When the no-profile baseline satisfies the documented runtime requirements, no profile-loaded PowerShell process is started.
  • When a profile is required, all PowerShell candidates share one total deadline.
  • A timeout preserves and installs the usable baseline environment.
  • Tests cover:
    • runtime already available without a profile;
    • runtime only available after profile loading;
    • a hanging pwsh.exe candidate;
    • fallback to powershell.exe within the remaining budget;
    • preservation of required FNM variables.
  • Traces record whether profile discovery was required, which candidate ran, its outcome, and the remaining or total budget.
Related work checked
  • #2951 removed the cmd.exe wrapper but did not change profile-loading policy.
  • #3383 added structured failures and a five-second per-command timeout, not a total discovery budget.
  • #4291 and #4221 bound server-side editor discovery; they do not affect desktop shell hydration.
  • #4228 improves Windows process diagnostics but does not affect this startup path.
  • #4395 tracks rejected duplicate launches leaving Electron processes and is intentionally separate from this issue.
  • #3611 concerns a WSL/runtime-version startup loop rather than desktop PowerShell discovery.

I did not find an open issue or PR specifically tracking unconditional Windows profile discovery or its additive candidate timeouts.

Impact

Major degradation or frequent failure

The delay occurs on every observed launch of the affected installation. It postpones all later desktop initialization and can add more than ten seconds before renderer/backend startup delays are considered.

Version or commit
T3 Code 0.0.29-nightly.20260722.878
Commit 9a0a07167f0623c3a7db0ffeff2e3939760309df
Electron 41.5.0
Environment
Windows 11 x64, build 26200.8875
PowerShell 7.6.4
Windows PowerShell 5.1
Node.js 22.14.0, already available from the no-profile PATH
Timezone: America/Bogota (UTC-05)
Logs or stack traces
Observed startup count: 23
Observation period: 2026-07-15 through 2026-07-23

No-profile read median:     2,064.0 ms
Profile-loaded read median: 4,201.4 ms
Total hydration median:     6,305.3 ms
Total hydration p95:       12,206.7 ms
Total hydration maximum:   13,348.0 ms

Profile timeouts observed: 2
Starts executing both profile modes: 23 of 23

Secrets, profile contents, and absolute user paths have been omitted.

Screenshots, recordings, or supporting files

No screenshot is needed for the startup-only behavior. A redacted trace extract can be attached if maintainers need the underlying span events.

Workaround

Temporarily simplifying expensive PowerShell profile initialization reduces the delay, but this changes the user’s shell configuration and is not a reliable application-level workaround.

Placing node.exe on the inherited or no-profile PATH does not prevent the current implementation from loading the profile.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with apps/desktop/src/shell/DesktopShellEnvironment.ts lines 297–353 and apps/desktop/src/app/DesktopApp.ts lines 229–243, then inspect the existing environment-reader tests referenced by the issue. Verify the baseline-first path, shared deadline for profile candidates, baseline preservation on timeout, and trace coverage for whether profile discovery was required.

Written by the indexing model from the issue text.

Assessment

Tech stack
electron, powershell, typescript
Domain
desktop
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.