Skip to content

Runtime Host candidate election storms and permanent unavailability once stored transcripts exceed the 45s ready window #5843

Description

@ggbdpq

Summary

When the stored transcript volume exceeds roughly 210-360MiB, the Runtime Host candidate startup exceeds its 45-second ready window, and every replacement candidate then loses the launch election and exits - the Host never becomes available, and the desktop shows RuntimeHostHandlerUnavailableError for every operation. 13+ consecutive candidate exits ({code: 2, signal: null}) were observed on one attempt.

Mechanism (all points verified in source)

  • process.exit(2) fires only on the election loser path (packages/runtime-host/src/candidate-entry.ts:107, result.kind === 'loser' after tryAcquireRuntimeHostLaunch fails to take the launch lease, server/candidate.ts:84). Exit 2 is therefore not a crash - it is the documented loser exit, taken silently (no startup diagnostic is written, so nothing lands on disk).
  • The desktop side waits electionDeadlineMs ?? 45_000 for the candidate to become ready (apps/desktop/src/main/runtime-host-desktop-candidate.ts:514).
  • With a ~360MiB store, the first candidate wins the lease and then spends more than 45s initializing storage; the manager declares failure and spawns a replacement; the winner still holds the lease, so every replacement loses the election and exits with 2. The handlers never come up.

Scale bisection (same fixture family, single-machine)

stored transcript volume Host candidate session open
4,000 turns (~70MiB) alive, serving normal
12,000 turns (~210MiB) alive (zero candidate exits, zero handler errors) see the sibling symptom below
12k + 4k + 4k (~360MiB) election storm (loser exits repeating) handlers never available

The threshold sits somewhere in 210-360MiB of total stored transcript volume.

Related

Fix directions (for discussion)

  • Scale the election deadline with store size (or make readiness, not a fixed window, decide the swap).
  • Let the lease winner keep the lease while initializing late, instead of forcing a losing replacement through the same window.
  • At minimum: write a startup diagnostic on the loser path - today a storm of exit 2s leaves no trace on disk, which is what made this hard to see.

Repro recipe

seed-helpers.ts with LARGE_HISTORY_TURN_COUNTS (12,000 / 4,000 / 4,000 turns x 18KiB replies) through the desktop candidate chain; archived spec, logs (13x exit-2 + handler-unavailable), and the bisect runs are available.

Activity

  1. Adarsh-Me commented on Oct 1, 2026

    @Adarsh-Me
    Contributor

    take

  2. Adarsh-Me commented on Oct 3, 2026

    @Adarsh-Me
    Contributor

    Status on #5843: my #5922 covers the permanent-unavailability half and #5845 shipped the loser diagnostic. One measured gap remains on main: connect-or-spawn.ts sets DEFAULT_ELECTION_DEADLINE_MS = 75_000 (line 75) while the desktop candidate waits readyTimeoutMs ?? 45_000 (line 514), and launch_election_lost is classified permanent (candidate-startup-failure.ts line 27). Should those two windows be one value? Happy to align them if 45s was not deliberate.

  3. ggbdpq commented on Oct 4, 2026

    @ggbdpq
    ContributorAuthor

    Dug into the history before answering: the 45s is not deliberate. readyTimeoutMs: input.electionDeadlineMs ?? 45_000 (runtime-host-desktop-candidate.ts:514) came in with the parity sweep #2216 as a plain fallback — no alignment argument anywhere in that change. The 75s side, by contrast, documents its reasoning in place (connect-or-spawn.ts:72-74): the Windows named-pipe ACL helper's fail-closed ceiling is 60s, plus room for election and connection bookkeeping.

    So please align — and from the #5843 fault path the mismatch is not neutral: with a 45s manager-side window under a 75s election window, the manager can declare a candidate dead while the election it lost is still inside its legitimate window. That is exactly the replace-and-reshuffle amplifier we measured (large-store winner initialization exceeding 45s → manager swaps → every replacement loses again). One concrete suggestion: rather than bumping 45→75 as a second magic number, have the desktop fallback reference the same constant/env channel the client already honors (MAKA_RUNTIME_HOST_ELECTION_DEADLINE_MS), so the two windows can't drift apart again.

    On launch_election_lost being classified permanent (candidate-startup-failure.ts:27): per-candidate that reads correct to me — a loser exits so the manager can spawn a fresh attempt — and the storm was caused by the window mismatch, not the classification. With the windows aligned I'd leave the classification alone, but that's a maintainer call.

  4. Adarsh-Me commented on Oct 4, 2026

    @Adarsh-Me
    Contributor

    Aligned in 000b70e on #5922. The Desktop fallback now resolves the caller value, then MAKA_RUNTIME_HOST_ELECTION_DEADLINE_MS, then DEFAULT_ELECTION_DEADLINE_MS, which the client exports. One constant, so the windows cannot drift again. The new desktop test fails 45000 !== 75000 without the fix; that suite is 27/27. Leaving launch_election_lost permanent, as you argued. A second 45s default sits at host-profile.ts:595, untouched.

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

Metadata

Metadata

Assignees

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