Skip to content

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

Description

@lmmontoya-ai

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 [Bug]: Duplicate launches can leave headless Electron process trees running on Windows #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

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions