You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by as...@apache.org on 2026/09/22 05:23:14 UTC

(maka) branch main updated: fix(runtime-host): preserve managed startup failure diagnostics (#5579)

This is an automated email from the ASF dual-hosted git repository.

Astro-Han pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/maka.git


The following commit(s) were added to refs/heads/main by this push:
     new e6db756890 fix(runtime-host): preserve managed startup failure diagnostics (#5579)
e6db756890 is described below

commit e6db756890c36a8d4396241cc4f3a6f180529d20
Author: Nyvo <cz...@gmail.com>
AuthorDate: Tue Sep 22 13:23:09 2026 +0800

    fix(runtime-host): preserve managed startup failure diagnostics (#5579)
    
    Enrich the managed-Host readiness failure with the supervisor-observable evidence: service state, PID, last exit code, and a bounded redacted log tail. The diagnostic rides the existing repair-handoff seam, so a Host that crashes before listening now names its actual failure instead of surfacing MANAGED_ROOT_REQUIRES_OPERATOR.
    
    Closes #5425.
---
 .../runtime-host-local-remote-access.test.ts       | 44 ++++++++++++++++++++
 .../__tests__/runtime-host-service-manager.test.ts | 20 +++++++++
 packages/cli/src/runtime-host-service-manager.ts   | 48 +++++++++++++++++++++-
 3 files changed, 111 insertions(+), 1 deletion(-)

diff --git a/apps/desktop/src/main/__tests__/runtime-host-local-remote-access.test.ts b/apps/desktop/src/main/__tests__/runtime-host-local-remote-access.test.ts
index 65f58c2f2e..9991253df0 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-local-remote-access.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-local-remote-access.test.ts
@@ -391,6 +391,50 @@ test('repairs an existing managed Host with the current setup package and restar
   assert.deepEqual(phases, ['checking', 'staging', 'restart']);
 });
 
+test('preserves service readiness evidence in the managed repair blocker', async (t) => {
+  const base = await mkdtemp(join(tmpdir(), 'maka-local-managed-repair-diagnostic-'));
+  t.after(() => rm(base, { recursive: true, force: true }));
+  const clientDataRoot = join(base, 'client');
+  const rootPath = join(clientDataRoot, 'workspaces', 'default');
+  const rootId = 'a'.repeat(64);
+  await mkdir(rootPath, { recursive: true });
+  await writeManagedLifecycle(clientDataRoot, rootPath, rootId);
+  const diagnostic = [
+    'Runtime Host service did not become ready: Host state is failed',
+    'service state: failed; active: false; pid: none; last exit code: 78',
+    'service logs (tail):\nstartup failed: [redacted]',
+  ].join('\n');
+  const service = createDesktopLocalRuntimeHostRemoteAccess({
+    ipcMain: { handle() {}, removeHandler() {} },
+    clientDataRoot,
+    rootPath,
+    rootId,
+    directPeerAvailable: true,
+    manager: () => undefined,
+    resolveSetupPackage: async () => ({ kind: 'npm', specifier: 'maka-agent@0.2.0' }),
+    resolveManagedDeploymentAuthority: async () => ({
+      kind: 'active',
+      lifecycleMode: 'supervised',
+      deploymentRoot: join(base, 'deployment'),
+      target: {
+        schemaVersion: 2,
+        serviceId: 'b'.repeat(64),
+        rootPath,
+        rootId,
+        operator: testOperator(join(base, 'operator.mjs')),
+        deploymentId: RECOVERY_DEPLOYMENT_ID,
+      },
+    }),
+    inspectHost: async () => ({ kind: 'unavailable' as const, reason: 'connect_failed' as const }),
+    operator: { async close() {} } as unknown as ReturnType<typeof createDesktopRuntimeHostLocalOperator>,
+  });
+  t.after(() => service.close());
+
+  const blocker = await service.resolveStartupRepair(new Error(diagnostic), new AbortController().signal);
+  assert.equal(blocker?.reason, 'repair');
+  assert.equal(blocker?.diagnostic, diagnostic);
+});
+
 test('replaces a conflicting supervised Host with the requested active-work policy', async (t) => {
   const base = await mkdtemp(join(tmpdir(), 'maka-local-managed-conflict-'));
   t.after(() => rm(base, { recursive: true, force: true }));
diff --git a/packages/cli/src/__tests__/runtime-host-service-manager.test.ts b/packages/cli/src/__tests__/runtime-host-service-manager.test.ts
index 4afe87e038..5ad9084e66 100644
--- a/packages/cli/src/__tests__/runtime-host-service-manager.test.ts
+++ b/packages/cli/src/__tests__/runtime-host-service-manager.test.ts
@@ -59,6 +59,7 @@ import { runManagedRuntimeHostUpdateCli } from '../runtime-host-update-command.j
 import {
   cleanupRuntimeHostManagedDeployment,
   effectiveRuntimeHostProjectDirectoryRoots,
+  formatRuntimeHostServiceReadinessDiagnostic,
   manageRuntimeHostService,
   replaceRuntimeHostManagedService,
   resolveRuntimeHostManagedServiceConfigPath,
@@ -3167,6 +3168,25 @@ describe('managed Runtime Host service', () => {
     await installing;
     assert.notEqual((await status).service.config, null);
   });
+
+  it('formats bounded, redacted readiness evidence for a failed managed Host', () => {
+    const diagnostic = formatRuntimeHostServiceReadinessDiagnostic({
+      lastFailure: 'Host state is failed; token=connection-secret',
+      status: {
+        state: 'failed',
+        active: false,
+        pid: null,
+        lastExitCode: 78,
+      },
+      logs: `${'x'.repeat(8_000)}\nstartup failed: {"apiKey":"fixture-secret"}`,
+    });
+
+    assert.match(diagnostic, /Host state is failed/u);
+    assert.match(diagnostic, /last exit code: 78/u);
+    assert.match(diagnostic, /service logs \(tail\):/u);
+    assert.doesNotMatch(diagnostic, /connection-secret|fixture-secret/u);
+    assert.ok(Buffer.byteLength(diagnostic, 'utf8') <= 2_048);
+  });
 });
 
 function legacySystemdUnitFixture(config: RuntimeHostManagedServiceConfig): string {
diff --git a/packages/cli/src/runtime-host-service-manager.ts b/packages/cli/src/runtime-host-service-manager.ts
index 598febd393..1447e8a810 100644
--- a/packages/cli/src/runtime-host-service-manager.ts
+++ b/packages/cli/src/runtime-host-service-manager.ts
@@ -35,6 +35,7 @@ import {
 import { homedir } from 'node:os';
 import { dirname, isAbsolute, join, resolve } from 'node:path';
 import { truncateUtf8 } from '@maka/core/diagnostic-log';
+import { redactSecrets } from '@maka/core/redaction';
 import {
   canonicalProjectDirectoryRootSpec,
   isCanonicalRuntimeHostWebSocketPath,
@@ -49,6 +50,7 @@ import {
 import {
   resolveRuntimeHostManagedServiceId,
   RUNTIME_HOST_SERVICE_LOG_MAX_BYTES,
+  RUNTIME_HOST_SERVICE_ERROR_MESSAGE_MAX_BYTES,
   type RuntimeHostReconciliationProvider,
   type RuntimeHostServiceErrorCode,
   type RuntimeHostSupervisorProvider,
@@ -78,6 +80,7 @@ const DEFAULT_WEBSOCKET_PATH = '/runtime-host';
 const SERVICE_OPERATION_LOCK_TIMEOUT_MS = 60_000;
 const SERVICE_READY_TIMEOUT_MS = 45_000;
 const SERVICE_READY_POLL_MS = 50;
+const SERVICE_READINESS_LOG_MAX_BYTES = 1_024;
 
 export interface RuntimeHostManagedServiceConfig {
   readonly schemaVersion: 1 | 2;
@@ -928,6 +931,34 @@ export function formatRuntimeHostServiceLogs(
     .join('\n');
 }
 
+export function formatRuntimeHostServiceReadinessDiagnostic(input: {
+  readonly lastFailure: string;
+  readonly status?: Pick<
+    RuntimeHostServiceBackendStatus,
+    'state' | 'active' | 'pid' | 'lastExitCode'
+  >;
+  readonly logs?: string;
+}): string {
+  const lines = [`Runtime Host service did not become ready: ${redactSecrets(input.lastFailure)}`];
+  if (input.status) {
+    lines.push(
+      `service state: ${input.status.state}; active: ${String(input.status.active)}; pid: ${input.status.pid ?? 'none'}; last exit code: ${input.status.lastExitCode ?? 'unknown'}`,
+    );
+  }
+  if (input.logs?.trim()) {
+    const safeLogs = takeUtf8Tail(
+      redactSecrets(input.logs.trim()),
+      SERVICE_READINESS_LOG_MAX_BYTES,
+    );
+    lines.push(`service logs (tail):\n${safeLogs}`);
+  }
+  return truncateUtf8(
+    lines.join('\n'),
+    RUNTIME_HOST_SERVICE_ERROR_MESSAGE_MAX_BYTES,
+    '\n<diagnostic truncated>',
+  );
+}
+
 function takeUtf8Tail(value: string, maximumBytes: number): string {
   const encoded = Buffer.from(value);
   if (encoded.byteLength <= maximumBytes) return value;
@@ -1313,10 +1344,25 @@ export async function verifyRuntimeHostManagedServiceReady(
   }
   throw new RuntimeHostServiceManagerError(
     'service_manager_operation_failed',
-    `Runtime Host service did not become ready: ${lastFailure}`,
+    await formatRuntimeHostServiceReadinessFailure(lastFailure, backend),
   );
 }
 
+async function formatRuntimeHostServiceReadinessFailure(
+  lastFailure: string,
+  backend: RuntimeHostServiceBackend,
+): Promise<string> {
+  const [status, logs] = await Promise.all([
+    backend.status().catch(() => undefined),
+    backend.logs().catch(() => undefined),
+  ]);
+  return formatRuntimeHostServiceReadinessDiagnostic({
+    lastFailure,
+    status,
+    logs,
+  });
+}
+
 async function prepareRuntimeHostRetirement(
   config: RuntimeHostManagedServiceConfig,
   expectedPid: number,


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org