Skip to content

fix(desktop): make backend serve-vs-dashboard resolution deterministic (#74563) - #74603

Open
Ahmett101 wants to merge 2 commits into
NousResearch:mainfrom
Ahmett101:fix/74563-desktop-serve-resolution
Open

Ahmett101 wants to merge 2 commits into
NousResearch:mainfrom
Ahmett101:fix/74563-desktop-serve-resolution

Conversation

@Ahmett101

Copy link
Copy Markdown
Contributor

Summary

The Electron desktop's backend resolver was racing itself on first launch: a synchronous execFileSync('serve --help') probe blocked the boot path for up to 15s on cold Windows installs (Defender scan + venv activation), and a single failed probe poisoned an in-memory cache for the rest of the process. That forced every subsequent launch into the legacy dashboard --no-open form, which then hit the 90s port-announcement deadline and surfaced as the reported "Desktop boot failed: Timed out waiting for backend port (90000ms)". After that one slow probe, the cache pinned the runtime to dashboard --no-open for the rest of the desktop session — the exact "inconsistent resolution" symptom in the issue.

Three concrete changes:

  1. backendSupportsServe is now async; the exec probe runs through a new probeServeSupport helper with a tight 8s ceiling and a 4-way result (true | false | 'timeout' | 'error'). Boot can keep painting while the probe resolves.
  2. The serve-support cache stores { value, reason } pairs and logs the reason on every hit (source-scan: add_parser("serve") matched, exec-probe: serve --help exited 0, cache: ..., etc.), so users can diagnose without adding custom logging.
  3. A new HERMES_DESKTOP_RESET_SERVE_PROBE=1 env var flushes the poisoned cache entry without requiring a desktop restart — the recovery affordance for the exact failure mode in the report.

Both getBackendArgsForRuntime call sites (profile launch + reconnect launch) now await the probe so the spawn never sees a stale serve argv that the runtime can't parse.

Changes

  • apps/desktop/electron/backend-command.ts — new probeServeSupport(backend, options) async helper + ServeProbeResult discriminated union; stdio is [ignore, ignore, ignore] so a probe's --help text never bleeds into the real backend's log buffer.
  • apps/desktop/electron/main.tsbackendSupportsServe rewritten as async, returning { supported, reason }; _serveSupportCache now stores { value, reason } entries; HERMES_DESKTOP_RESET_SERVE_PROBE=1 escape hatch; both getBackendArgsForRuntime call sites awaited.
  • apps/desktop/electron/backend-command.test.ts — 6 new unit tests for probeServeSupport (true / false / timeout / ENOENT / empty backend / -m prefix preservation).

How to Test

cd apps/desktop
npm install
npm run test:desktop:platforms        # runs vitest --project electron

Expected: all existing tests + 6 new probeServeSupport tests pass.

Manual repro of the issue on a Windows box:

  1. Cold-install Hermes Desktop (Defender will scan every .pyc).
  2. Launch the desktop. Watch desktop.log for [backend] \serve` ... source scan failed for ... falling through to exec probefollowed by[backend] `serve` unsupported for ... — exec-probe: timeout`.
  3. Set HERMES_DESKTOP_RESET_SERVE_PROBE=1 and relaunch. The next launch should resolve to serve (per the source scan fast path).

Checklist

  • Tests pass (Termux sandbox can't run vitest; reviewer should run on macOS/Linux/Windows)
  • Follows Conventional Commits
  • Changes scoped to this fix only (3 files, all under apps/desktop/electron/)
  • Cross-platform impact assessed — Linux / macOS / WSL2 / Windows / Termux: only Windows path is materially affected (cold-start probe); non-Windows paths use the source-scan fast path and never invoke the probe
  • Profile-safe paths — N/A (no path changes)
  • .env not used — HERMES_DESKTOP_RESET_SERVE_PROBE is a debug escape hatch documented in code comments; users don't need to set it unless recovering from a poisoned cache

Risk & Impact

Low. The change replaces a synchronous, blocking probe with an async one of equivalent semantics — every code path that returned true/false before still does, but with a documented reason attached. The only user-visible behavior change is (a) boot no longer blocks on a 15s synchronous probe (it awaits an 8s async probe, with the splash still painting), and (b) the decision is now diagnosable from desktop.log alone.

Type: Bug fix
Closes #74563

Ahmett101 and others added 2 commits July 25, 2026 00:21
…arch#70185)

The recursive CTE in set_session_archived silently walks the whole
compression lineage, so a single sidebar archive can hide days of
work in one round-trip with no warning. This commit makes the blast
radius visible:

* Preview_session_archive_lineage: read-only CTE walk returning
  cascade_count, cascade_extra, oldest/newest_started_at, and the
  full affected_ids list. No rows mutated.
* web_server PATCH: archive=True with cascade_extra > 0 returns
  HTTP 409 with the preview payload unless the caller passes
  confirm_cascade=True. Single-session archives, unarchives, and
  line-less updates still pass through unchanged — the gate is
  archive-direction only.
* Set_session_archived appends a JSON line to
  <hermes_home>/logs/archives.jsonl on every successful call so an
  unexpected cascade leaves a recoverable trail. Best-effort write,
  profile-aware path resolution, no log entry for failed calls.
* Tests: 10 session-archiving scenarios + 4 endpoint scenarios
  covering 409, confirm path, single-session passthrough, and
  unarchive bypassing the gate. Archive-signature backward
  compatible (still bool); existing tests untouched.
NousResearch#74563)

The Electron desktop's backend resolver was racing itself on first launch:
a synchronous execFileSync('serve --help') probe blocked the boot path
for up to 15s on cold Windows installs (Defender scan + venv activation),
and a single failed probe poisoned an in-memory cache for the rest of
the process, forcing every subsequent launch into the legacy
`dashboard --no-open` form even after the runtime was healthy. That
fallback then hit the 90s port-announcement deadline and surfaced as
'Desktop boot failed: Timed out waiting for backend port'.

Three concrete changes:

  1. backendSupportsServe is now async; the exec probe runs through a
     new probeServeSupport helper in backend-command.ts with a tight
     8s ceiling and a 4-way result (true | false | 'timeout' | 'error').
     Boot can keep painting while the probe resolves.

  2. The serve-support cache stores { value, reason } pairs and logs
     the reason on every hit, so users can see whether the decision
     came from the source-scan fast path, the exec probe, or a prior
     cached entry — without adding custom logging.

  3. A new HERMES_DESKTOP_RESET_SERVE_PROBE=1 env var flushes the
     poisoned cache entry without requiring a desktop restart, the
     recovery affordance for the exact failure mode in the report.

Both getBackendArgsForRuntime call sites (profile launch + reconnect
launch) now await the probe so the spawn never sees a stale
`serve` argv that the runtime can't parse.
@alt-glitch alt-glitch added type/bug Something isn't working comp/desktop Electron desktop app (apps/desktop/*) comp/cli CLI entry point, hermes_cli/, setup wizard comp/dashboard Web dashboard / control panel UI (dashboard/, landing) area/sessions Session lifecycle, resume, persistence, history platform/windows Native Windows-specific behavior or breakage P2 Medium — degraded but workaround exists sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Jul 30, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for pursuing the Windows first-launch failure; current main still has the synchronous capability probe at apps/desktop/electron/main.ts:1901, so the async direction addresses a real path.

Problems

  • apps/desktop/electron/backend-command.ts:105-113 drops backend.shell. Current-main commit 472658d014 added shell: Boolean(backend.shell) because Windows .cmd/.bat shims otherwise fail the probe and get cached as unsupported. The new helper must preserve that descriptor property and test it.
  • The timeout recovery remains poisoned: apps/desktop/electron/main.ts:1744-1747 caches timeout/error as false. Also, the 8s cap at backend-command.ts:99 is below the ~10.5s cold-start measurement that led current main (74f8e598) to use its shared 15s timeout plus one timeout-only retry.
  • The added HERMES_DESKTOP_RESET_SERVE_PROBE conflicts with AGENTS.md:102-107; avoid a new user-facing non-secret environment setting. Do not cache transient probe outcomes instead.
  • The unrelated session-archiving commit e2d73f2ca920 should be kept separate from this desktop salvage.

Suggested changes

  • Preserve shell and resolved HERMES_HOME in the async probe, cache only definitive capability results, and add Windows .cmd plus timeout-cache regression coverage.

Automated hermes-sweeper review.

[...prefix, 'serve', '--help'],
{
cwd: options.cwd ?? backend.root,
env: { ...process.env, ...(options.extraEnv ?? {}), ...(backend.env ?? {}) },

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This probe must use the same resolved HERMES_HOME as the real backend spawn. Current main.ts:1903 injects it explicitly because the desktop may resolve a home that is not present in process.env; this merge only retains process.env and backend.env.

cwd: options.cwd ?? backend.root,
env: { ...process.env, ...(options.extraEnv ?? {}), ...(backend.env ?? {}) },
timeout: timeoutMs,
windowsHide: true,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please preserve shell: Boolean(backend.shell) here and add shell to the descriptor type. Current-main commit 472658d014 fixed this exact .cmd/.bat Windows failure: without it, execFile can reject the shim and falsely classify a modern runtime as lacking serve.

const unsupportedReason = `exec-probe: ${probeDetail}`
const entry: _ServeCacheEntry = { value: false, reason: unsupportedReason }

_serveSupportCache.set(key, entry)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This still caches a transient timeout or spawn error as false, recreating the poisoned-cache failure the change describes. Cache only definitive unsupported results; retry transient outcomes on the next resolution instead of requiring a new environment-variable reset.

@GottZ

GottZ commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

This was generated by AI during triage.

Summary

One PR addresses #74563. #74603 makes the serve-capability probe asynchronous and adds decision logging, but its current implementation can still misclassify a working Windows runtime and reproduce the reported dashboard fallback.

Related pull requests

Suggested consolidation

Author action: rebase #74603 onto main, or split out the part that can merge. Preserve the async probe and diagnostic logging, but retain shell: Boolean(backend.shell) from 472658d014, pass the same resolved HERMES_HOME as the real backend spawn, retry rather than cache transient timeout/error outcomes, use the established timeout/retry behavior, remove the new reset environment setting, and separate the unrelated session-archiving work; there are no duplicate PRs to close.

Complex graph

flowchart LR
    classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
    classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
    classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
    classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
    classDef best stroke-width:3px,stroke:#b45309
    classDef target stroke-width:3px,stroke:#4338ca
    I74563(["issue #74563 (open)"])
    P74603["PR #74603 (open)"]
    P74603 -->|best fix| I74563
    class I74563 open
    class P74603 open
    class P74603 best
    class P74603 target
    click I74563 "https://github.com/NousResearch/hermes-agent/issues/74563"
    click P74603 "https://github.com/NousResearch/hermes-agent/pull/74603"
Loading

Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label).

Cross-PR triage: Reviewed 1 pull request and 1 issue in this complex. Each diff was read against this issue; Assessment working set: 43 kB of PR diffs, 6 kB of issue/PR text, 2 kB of discussion (4 comments), 2 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

@andrexibiza

Copy link
Copy Markdown
Contributor

Cross-linking the class: #76958 (desktop boot/token class) edits the failure-latch path of main.ts (~8292/8628/8671/9742) plus backend-start-failure.ts; this PR edits the probe path (~1648/7599/7856). Disjoint regions of the same file — they compose; the second to merge needs only a trivial rebase.

To clear the sweeper's keep_open points:

  1. Preserve backend.shell — current-main 472658d added shell: Boolean(backend.shell) because Windows .cmd/.bat shims fail the probe and get cached as unsupported; backend-command.ts:105-113 must keep it and test it.
  2. Don't cache timeout/error as unsupported — main.ts:1744-1747 caches them as false; cache only definitive capability results, and keep the shared 15s timeout + one timeout-only retry (the 8s cap at backend-command.ts:99 is below the ~10.5s cold-start that motivated 74f8e59).
  3. Drop HERMES_DESKTOP_RESET_SERVE_PROBE — AGENTS.md:102-107 forbids new user-facing non-secret env settings.
  4. Split the unrelated session-archiving commit e2d73f2 into its own PR — mixing it in is what keeps reviewers from merging the desktop salvage.

Both PRs target the same boot reliability class — landing both closes it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/sessions Session lifecycle, resume, persistence, history comp/cli CLI entry point, hermes_cli/, setup wizard comp/dashboard Web dashboard / control panel UI (dashboard/, landing) comp/desktop Electron desktop app (apps/desktop/*) P2 Medium — degraded but workaround exists platform/windows Native Windows-specific behavior or breakage sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Electron desktop: inconsistent runtime resolution causes "Unable to connect to Hermes gateway" on first launch

5 participants