Skip to content

bug(runtime-host): crash-looping managed Host is misreported as MANAGED_ROOT_REQUIRES_OPERATOR #5425

Description

@MasamiYui

What happened

When a managed Runtime Host is installed but its process dies during startup, the Desktop handoff surface reports MANAGED_ROOT_REQUIRES_OPERATOR and asks the user to "activate it through the configured Host profile". The operator is in fact already running — it is starting the Host, and the Host is exiting immediately. The real cause is written to the service's stderr log and never reaches any surface.

This is filed for discussion rather than as a concrete fix proposal; the classification and the diagnostic plumbing are separable, and I am not sure which the project wants to change.

I hit this through a specific cause (a pinned Node runtime that cannot load @maka/storage, filed as #5424), but the misreport is independent of why the Host exits — anything that kills it before it can listen produces the same result.

Why the classification lands where it does. With the deployment installed and the Host process gone, no endpoint answers, so the client reaches the launch branch in connectOrSpawn, reads the active deployment record, and finds no launch claim:

// packages/runtime-host/src/client/connect-or-spawn.ts:446
const managedLaunchRejection = runtimeHostManagedLaunchRejection(
  managedDeployment,
  managedLaunchClaim,
  'on_demand',
);
if (managedLaunchRejection !== undefined) {
  return {
    kind: 'failed',
    reason:
      managedLaunchRejection === 'managed_root_requires_operator' && sawUnresponsiveEndpoint
        ? 'host_unresponsive'
        : managedLaunchRejection,
  };
}

The sawUnresponsiveEndpoint refinement immediately above already exists for the adjacent case — a managed endpoint that accepts a connection but does not answer. It does not cover this one, because when the Host dies before listening, no connection is ever accepted and sawUnresponsiveEndpoint stays false. The refusal is technically correct (this client genuinely may not spawn a replacement), but as a user-facing diagnosis it points at the wrong thing.

Why the real cause never surfaces. Three separate places drop it:

  1. The Host can exit before any of its own code runs — an unresolved static ESM import, for example — so classifyCandidateStartupFailure (packages/runtime-host/src/candidate-startup-failure.ts) has no error to classify and no exit code to map. CANDIDATE_STARTUP_FAILURE_REASONS has no entry for "the runtime could not load the Host at all".
  2. verifyRuntimeHostManagedServiceReady reports only its own connect failure:
    // packages/cli/src/runtime-host-service-manager.ts:1336
    throw new RuntimeHostServiceManagerError(
      'service_manager_operation_failed',
      `Runtime Host service did not become ready: ${lastFailure}`,
    );
    lastFailure is a client-side connect/handshake message (not available), never the service's exit status or stderr. Install, update, and repair all fail through this path, so a correctly rolled-back update reports nothing about why the new build did not come up.
  3. The Desktop repair blocker carries only the startup error text:
    // apps/desktop/src/main/runtime-host-local-remote-access.ts:844
    diagnostic: error.message,
    resolveStartupRepair already queries the backend for activity and registration a few lines earlier, so service state is available at that point; the failure evidence is not attached.

The user-visible result is a repair dialog that loops: it offers a repair, the repair stages the next package, readiness verification times out, it rolls back, and the same MANAGED_ROOT_REQUIRES_OPERATOR text comes back. Nothing in the product ever names the actual failure, and maka runtime-host service logs — which would show it — is not reachable from the surface that is blocking.

How to reproduce

Any cause that makes the managed Host exit before it listens will do. The one I hit:

  1. Install a supervised managed Runtime Host pinned to a Node that cannot load @maka/storage (see bug(cli): managed deployment pins a Node runtime without zstd support #5424 for the specific conditions).
  2. The LaunchAgent restarts the Host every ~2s; launchctl list | grep maka shows the pid changing.
  3. Desktop shows a repair handoff whose diagnostic is MANAGED_ROOT_REQUIRES_OPERATOR.
  4. Accept the repair. It stages the newer package, fails readiness, rolls back, and the dialog returns with the same diagnostic.
  5. The actual SyntaxError is only in ~/Library/Logs/Maka/runtime-host-services/com.maka.runtime-host.<rootId>.stderr.log.

A cheaper artificial reproduction should be possible by making the configured Host entry point exit non-zero at startup.

Environment

  • Maka version: Desktop 0.2.0-dev.39.20260916, managed Runtime Host package 0.2.0-dev.38.20260916
  • Repository commit for the line references above: fa25b3d01
  • OS: macOS (Darwin 25.6.0), arm64
  • Surface: Desktop + Runtime Host (supervised LaunchAgent deployment)

Logs, screenshots, or additional context

The handoff view the user is left with — note that operation: "repair" and reason: "repair_required" are correct, while diagnostic describes a different problem:

{
  "target": { "name": "…", "location": "local", "rootId": "72e2fe36…" },
  "state": "progress",
  "reason": "repair_required",
  "mayExitNaturally": false,
  "operation": "repair",
  "actions": ["cancel"],
  "defaultAction": "cancel",
  "phase": "checking",
  "diagnostic": "This workspace is managed by a Runtime Host operator. Activate it through the configured Host profile. Diagnostic code: MANAGED_ROOT_REQUIRES_OPERATOR."
}

Directions that seem worth weighing, roughly in increasing cost — I have no attachment to any of them and would rather hear what fits the intended model before writing code:

  • Distinguish "managed but not running" from "managed, must go through the operator." Extend the existing sawUnresponsiveEndpoint refinement so that a registered deployment whose process is absent or repeatedly exiting reports something like managed_service_not_running instead. Smallest change, and it alone would stop pointing users at the Host profile.
  • Give an unloadable runtime a classifiable failure. A preflight in the CLI entry point, before any static import of @maka/storage, could exit with a dedicated reason in CANDIDATE_STARTUP_FAILURE_REASONS / EXIT_CODE_BY_REASON so candidateStartupFailureForExitCode can recover it. This only helps causes the entry point can anticipate, which may make it the wrong shape of fix.
  • Carry the evidence. Attach the backend's last exit status and a bounded, redacted tail of the service stderr to the readiness failure and to the repair blocker's diagnostic. redactSecrets and the 8 KiB bound already applied in projectBlocker give a precedent for how much is safe to surface. This is the change that would actually have told me what was wrong, but it is also the one most likely to have policy constraints I cannot see.

Happy to take any of these on if there is a preferred direction.

Investigated with Claude Code (Opus 5); the behaviour and line references above were verified against the source at fa25b3d01 and against a live reproduction on the affected machine.

Activity

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions