fix(windows): find provider CLIs from User PATH - #7362
Conversation
Electron often inherits a stale User PATH, so Settings still reports cursor-agent missing after the irm install. Read Machine+User PATH and include the official cursor-agent directory. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Warning Your free Security trial is over. An organization admin can activate Security or dismiss this notice. Comment |
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This PR combines a Windows-wide PATH-resolution change with a substantial, enabled-by-default Hornet HTTP provider and model/settings integration. The new runtime path has unresolved lifecycle, cancellation, model-routing, health-status, and UI integration risks that require human review. Not approved because:
Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more. |
Electron's process PATH and the PowerShell one-arg probe miss User/Machine registry Path, so every Settings detector stays Not found. Read HKCU/HKLM Path via reg.exe and merge it in desktop and server PATH repair. Co-authored-by: Cursor <cursoragent@cursor.com>
…istent PATH Electron on Windows inherits a stale PATH, so freshly installed provider CLIs (cursor-agent, grok, codex) showed as missing until relaunch; read HKCU/HKLM persistent PATH as a fallback. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PowerShell PATH capture joined Machine, User, then Process, so profile scripts that prepend toolchain dirs (fnm, etc.) lost to system installs. Join Process first, then Machine and User as supplements. Co-authored-by: Ryan Johnson <AMDphreak@users.noreply.github.com>
Wire contracts, server adapter, and web icon so t3code can use Hornet as an interim multi-machine provider while desktop runs hornet serve. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Effect service conventions review of the changed TypeScript. One finding in the new Hornet adapter: an error detail built from the stringified underlying cause.
Posted via Macroscope — Effect Service Conventions
| new ProviderAdapterProcessError({ | ||
| provider: PROVIDER, | ||
| threadId, | ||
| detail: `Hornet request failed: ${String(cause)}`, |
There was a problem hiding this comment.
detail here is just a stringified cause, so ProviderAdapterProcessError.message ends up derived from the underlying failure instead of stable structural attributes (and String(cause) can splice raw request/URL text into a caller-visible message). The real error is already preserved as cause; consider deriving detail from the structural context (path) only.
| detail: `Hornet request failed: ${String(cause)}`, | |
| detail: `Hornet ${path} request failed.`, |
Posted via Macroscope — Effect Service Conventions
| })), | ||
| ), | ||
|
|
||
| rollbackThread: (threadId, numTurns) => |
There was a problem hiding this comment.
🟡 Medium Layers/HornetAdapter.ts:338
rollbackThread reports success after truncating only ctx.turns, but the Hornet session remains unchanged. Subsequent sendTurn calls still post the same ctx.nodeId, so Hornet uses the discarded turns and replies with stale context despite the application reporting a rollback. This must invoke Hornet's rollback operation (and update the session cursor if required), or return an explicit unsupported error.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/Layers/HornetAdapter.ts around line 338:
`rollbackThread` reports success after truncating only `ctx.turns`, but the Hornet session remains unchanged. Subsequent `sendTurn` calls still post the same `ctx.nodeId`, so Hornet uses the discarded turns and replies with stale context despite the application reporting a rollback. This must invoke Hornet's rollback operation (and update the session cursor if required), or return an explicit unsupported error.
| raw: { source: "hornet.http", method: "turn.started", payload: { turnId } }, | ||
| }); | ||
|
|
||
| const result = yield* postJson<{ |
There was a problem hiding this comment.
🟡 Medium Layers/HornetAdapter.ts:233
When postJson("/api/provider/turn", ...) fails, readThread still returns the empty turn appended at line 221, so rollback and checkpoint logic treat a failed request as conversation history. Wrap the request failure path to remove that turn and publish turn.aborted before propagating the error.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/Layers/HornetAdapter.ts around line 233:
When `postJson("/api/provider/turn", ...)` fails, `readThread` still returns the empty turn appended at line 221, so rollback and checkpoint logic treat a failed request as conversation history. Wrap the request failure path to remove that turn and publish `turn.aborted` before propagating the error.
| const body = yield* http.execute(HttpClientRequest.get(`${baseUrl}/api/health`)).pipe( | ||
| Effect.flatMap((response) => response.json), | ||
| Effect.timeout("3 seconds"), |
There was a problem hiding this comment.
🟡 Medium Layers/HornetProvider.ts:149
A non-2xx response containing { "ok": true } is published as ready and authenticated, so an erroring Hornet server is shown as usable. HttpClient.execute exposes the HTTP response independently of its status, but this code trusts the JSON body without checking response.status; require a successful status before parsing and accepting ok.
const body = yield* http.execute(HttpClientRequest.get(`${baseUrl}/api/health`)).pipe(
- Effect.flatMap((response) => response.json),
+ Effect.flatMap((response) =>
+ response.status >= 200 && response.status < 300 ? response.json : Effect.succeed(null),
+ ),🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/Layers/HornetProvider.ts around lines 149-151:
A non-2xx response containing `{ "ok": true }` is published as `ready` and `authenticated`, so an erroring Hornet server is shown as usable. `HttpClient.execute` exposes the HTTP response independently of its status, but this code trusts the JSON body without checking `response.status`; require a successful status before parsing and accepting `ok`.
| respondToRequest: () => Effect.void, | ||
| respondToUserInput: () => Effect.void, | ||
|
|
||
| stopSession: (threadId) => |
There was a problem hiding this comment.
🟡 Medium Layers/HornetAdapter.ts:311
stopSession and interruptTurn do not cancel in-flight sendTurn work, so a request that passed requireSession before either operation still publishes content.delta and turn.completed after the session or turn was stopped. Track active turn fibers/requests and invalidate the session or turn before publishing completion events.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/Layers/HornetAdapter.ts around line 311:
`stopSession` and `interruptTurn` do not cancel in-flight `sendTurn` work, so a request that passed `requireSession` before either operation still publishes `content.delta` and `turn.completed` after the session or turn was stopped. Track active turn fibers/requests and invalidate the session or turn before publishing completion events.
There was a problem hiding this comment.
One finding: the new hornet provider is exposed in the chat provider picker but is not registered in the web settings provider metadata, so it cannot be configured from Settings → Providers.
Posted via Macroscope — UI Consistency
| { | ||
| value: ProviderDriverKind.make("hornet"), | ||
| label: "Hornet", | ||
| available: true, | ||
| pickerSidebarBadge: "new", | ||
| }, |
There was a problem hiding this comment.
Hornet is now offered in the provider picker, but there is no matching entry in PROVIDER_CLIENT_DEFINITIONS (apps/web/src/components/settings/providerDriverMeta.ts), unlike every other available option (codex, claudeAgent, opencode, cursor, grok).
Because ProviderSettingsPanel derives PROVIDER_SETTINGS from DRIVER_OPTIONS, Settings → Providers renders no Hornet default row at all, AddProviderInstanceDialog cannot create a Hornet instance, and any instance that does exist falls through the unknown/fork-driver path in ProviderInstanceCard: raw hornet label, no icon, no Early Access badge (the server snapshot advertises one), no settings form — so serverUrl/binaryPath are uneditable — and no models section.
Suggested fix: add a Hornet definition next to the grok entry in providerDriverMeta.ts (importing HornetSettings and HornetIcon):
{
value: ProviderDriverKind.make("hornet"),
label: "Hornet",
icon: HornetIcon,
badgeLabel: "Early Access",
settingsSchema: HornetSettings,
},Posted via Macroscope — UI Consistency
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 4 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want fixes drafted automatically? Bugbot Autofix can create code changes for findings. A team admin can enable Autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 8e7b59d. Configure here.
| input: text, | ||
| }, | ||
| input.threadId, | ||
| ); |
There was a problem hiding this comment.
Failed Hornet turns stay open
High Severity
sendTurn publishes turn.started before POST /api/provider/turn. If that request fails, the effect errors and no turn.completed, turn.failed, or turn.aborted event is emitted. Ingestion and checkpointing already observed the start, so the thread can remain in progress after the adapter failure is recovered.
Reviewed by Cursor Bugbot for commit 8e7b59d. Configure here.
| }); | ||
| }), | ||
| ), | ||
| ), |
There was a problem hiding this comment.
Interrupt cannot stop Hornet turns
High Severity
interruptTurn only publishes turn.aborted. It does not cancel the in-flight POST /api/provider/turn, and that request still publishes content.delta plus turn.completed when it returns. Stop therefore races with a later completion, so the UI can show an aborted turn that then finishes with the full assistant text.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 8e7b59d. Configure here.
| { | ||
| sessionId: ctx.nodeId, | ||
| input: text, | ||
| }, |
There was a problem hiding this comment.
Selected Hornet model is dropped
Medium Severity
The adapter advertises sessionModelSwitch: "unsupported" and the contracts layer adds Hornet defaults plus aliases, but neither startSession nor sendTurn forwards modelSelection. Every turn hits the desk with only sessionId and input, so picker choices such as GLM, MiniMax, or Kimi never reach Hornet.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 8e7b59d. Configure here.
| label: "Hornet", | ||
| available: true, | ||
| pickerSidebarBadge: "new", | ||
| }, |
There was a problem hiding this comment.
Hornet omitted from settings catalog
Medium Severity
Hornet is registered as a built-in driver and added to the chat picker, but PROVIDER_CLIENT_DEFINITIONS in providerDriverMeta.ts was not updated. Settings therefore has no Hornet schema, icon, or add-instance option, so serverUrl and binaryPath cannot be edited in the UI even though every install hydrates a default Hornet instance.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 8e7b59d. Configure here.
|
Note 🤖 GPT-6 Astra (preview) responding on behalf of Theo This was closed as part of an automated cleanup pass. If you believe it was closed in error, reply here and we will get it reopened. Closing this combined PR in favor of #8465 for Windows provider discovery. The PATH cases are recorded there for review. This branch also enables a separate Hornet provider, with unresolved stop and failure behavior, which does not belong in the PATH fix. Windows discovery work continues in the retained PR. |


Summary
Hi! On Windows, Settings → Providers reports every local CLI as missing (
cursor-agent,opencode,grok,codex, …) even when those binaries are on User PATH and a new PowerShell window can run them. Restarting T3 does not help.This is Windows-only. Electron inherits a stale process PATH, and the PowerShell probe used
[Environment]::GetEnvironmentVariable('PATH')(process env), not User/Machine registry. macOS/Linux login-shell probing is a different path.This PR tries to make User PATH visible to every provider detector:
Pathviareg.exe(does not depend on spawning PowerShell)fixPathpath and the desktop shellFixes #7360
Happy to adjust anything that doesn’t fit the project’s taste.
Test plan
vp test run packages/shared/src/shell.test.ts apps/desktop/src/shell/DesktopShellEnvironment.test.tsopencode/grok/codex/cursor-agenton User PATH, launch T3 from a stale process PATH; those providers should become installed/ready withoutbinaryPathoverridesMade with Cursor (Auto).
Note
Medium Risk
Windows PATH merge order changes which binaries resolve for every provider probe; Hornet adds a new HTTP-backed runtime path but is isolated to the new driver.
Overview
Fixes Windows provider CLI detection when Electron inherits a stale process
PATH. The shared shell layer now reads persistent User and MachinePathfrom the registry viareg.exe, exposes it throughWindowsPersistentPath, and merges that ahead of shell/inherited segments in bothresolveWindowsEnvironmentand the desktopDesktopShellEnvironmentinstall path. PowerShell env capture forPATHnow joins Process, Machine, and User (not process-only), with Windows-specific helpers moved/consolidated in@t3tools/shared/shell.resolveKnownWindowsCliDirsalso adds%LOCALAPPDATA%\cursor-agent. Tests cover registry parsing and stale-PATH scenarios.In the same change set, adds a built-in
hornetprovider:HornetSettings(defaultserverUrl), HTTPHornetAdapteragainst/api/provider/*, health probing, driver registration, contracts/model aliases, heuristicHornetTextGeneration, and web picker/icon/docs for six built-in drivers.Reviewed by Cursor Bugbot for commit 8e7b59d. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Fix Windows User PATH resolution for provider CLIs and add
hornetprovider driverreadWindowsUserAndMachinePathin shell.ts, merging them ahead of known CLI dirs and no-profile PATH so GUI-launched processes find provider CLIs installed in User PATH.cursor-agentto known Windows CLI dirs inresolveKnownWindowsCliDirs.hornetbuilt-in provider:HornetDriverin HornetDriver.ts, an HTTP adapter with session/turn streaming in HornetAdapter.ts, provider status probing in HornetProvider.ts, heuristic text generation in HornetTextGeneration.ts, settings schema in settings.ts, model defaults/aliases in model.ts, and web UI icon + provider picker entry.hornetinBUILT_IN_DRIVERSand extendsRuntimeEventRawSourcewithhornet.http.resolveWindowsEnvironmentandinstallWindowsEnvironmentnow prepend registry User/Machine PATH entries to the merged PATH, which may change command resolution order on Windows for GUI-launched desktop processes.Macroscope summarized 8e7b59d.