// Write Cli Startup Metadata tests cover write cli startup metadata script behavior.
import { spawn } from "node:child_process";
import { EventEmitter } from "node:events";
import fs, { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { availableParallelism } from "node:os";
import path from "node:path";
import { PassThrough } from "node:stream";
import { pathToFileURL } from "node:url";
import { describe, expect, it, vi } from "vitest";
import { resolveWindowsTaskkillPath } from "../../scripts/lib/windows-taskkill.mjs";
import { testing } from "../../scripts/write-cli-startup-metadata.ts";
import { waitForPidFile } from "../helpers/process-wait.js";
import { createScriptTestHarness } from "./test-helpers.js";

vi.mock("node:child_process", async (importOriginal) => {
  const actual = await importOriginal<typeof import("node:child_process")>();
  return { ...actual, spawn: vi.fn(actual.spawn) };
});

// These subprocess tests use explicit ready/close signals; timeout only catches broken fixtures.
const LOAD_SENSITIVE_PROCESS_TIMEOUT_MS = process.env.CI ? 30_000 : 15_000;
const COMMAND_HELP_RENDER_CONCURRENCY = Math.min(8, Math.max(2, availableParallelism()));
const DEFAULT_COMMAND_HELP_NAMES = [
  "browser",
  "secrets",
  "nodes",
  "doctor",
  "gateway",
  "models",
  "plugins",
  "sessions",
  "tasks",
] as const;

function writeFixtureFile(rootDir: string, relativePath: string, contents: string): void {
  const filePath = path.join(rootDir, relativePath);
  mkdirSync(path.dirname(filePath), { recursive: true });
  writeFileSync(filePath, contents, "utf8");
}

function writeStartupMetadataSourceSignatureFixture(rootDir: string): void {
  const fixtures = new Map<string, string>([
    ["extensions/browser/src/cli/browser-cli.ts", "export const browserHelp = 'browser';\n"],
    ["extensions/canvas/cli-metadata.ts", "export const canvasMetadata = 'canvas';\n"],
    ["extensions/canvas/index.ts", "export const canvasEntry = 'canvas';\n"],
    ["extensions/canvas/src/a2ui-jsonl.ts", "export const a2uiJsonl = 'canvas';\n"],
    ["extensions/canvas/src/cli-helpers.ts", "export const canvasHelpers = 'canvas';\n"],
    ["extensions/canvas/src/cli.ts", "export const canvasCliHelp = 'canvas';\n"],
    ["src/cli/banner.ts", "export const banner = 'openclaw';\n"],
    [
      "src/cli/daemon-cli/register-service-commands.ts",
      "export const gatewayServiceCommands = 'gateway';\n",
    ],
    ["src/cli/gateway-cli.ts", "export const gatewayHelp = 'gateway';\n"],
    ["src/cli/gateway-cli/register.ts", "export const gatewayRegister = 'gateway';\n"],
    ["src/cli/gateway-cli/run-command.ts", "export const gatewayRun = 'gateway';\n"],
    ["src/cli/help-format.ts", "export const helpFormat = 'help';\n"],
    ["src/cli/models-cli.ts", "export const modelsHelp = 'models';\n"],
    ["src/cli/nodes-cli/register.ts", "export const nodesHelp = 'nodes';\n"],
    ["src/cli/program/register.maintenance.ts", "export const maintenanceHelp = 'maintenance';\n"],
    [
      "src/cli/program/register.status-health-sessions.ts",
      "export const statusHealthSessionsHelp = 'sessions';\n",
    ],
    ["src/cli/program/context.ts", "export const context = 'context';\n"],
    ["src/cli/program/help.ts", "export const help = 'help';\n"],
    ["src/cli/plugins-cli.ts", "export const pluginsHelp = 'plugins';\n"],
    [
      "src/plugins/register-plugin-cli-command-groups.ts",
      "export const pluginCommandGroups = 'plugins';\n",
    ],
    ["src/cli/secrets-cli.ts", "export const secretsHelp = 'secrets';\n"],
    ["packages/terminal-core/src/links.ts", "export const links = 'links';\n"],
    ["packages/terminal-core/src/theme.ts", "export const theme = 'theme';\n"],
  ]);
  for (const [relativePath, contents] of fixtures) {
    writeFixtureFile(rootDir, relativePath, contents);
  }
}

function processIsAlive(pid: number): boolean {
  try {
    process.kill(pid, 0);
    return true;
  } catch (error) {
    return (error as NodeJS.ErrnoException).code === "EPERM";
  }
}

function expectedTaskkillPath(): string {
  return resolveWindowsTaskkillPath();
}

function createSpawnTextChild() {
  return Object.assign(new EventEmitter(), {
    kill: vi.fn((_signal?: NodeJS.Signals) => true),
    stderr: new PassThrough(),
    stdout: new PassThrough(),
  });
}

async function waitForProcessExit(
  pid: number,
  timeoutMs = LOAD_SENSITIVE_PROCESS_TIMEOUT_MS,
): Promise<void> {
  const deadline = Date.now() + timeoutMs;
  while (Date.now() < deadline) {
    if (!processIsAlive(pid)) {
      return;
    }
    await new Promise((resolve) => {
      setTimeout(resolve, 10);
    });
  }
  throw new Error(`process ${pid} was still alive after ${timeoutMs}ms`);
}

async function waitForChildClose(
  child: ReturnType<typeof spawn>,
  timeoutMs = LOAD_SENSITIVE_PROCESS_TIMEOUT_MS,
): Promise<{ code: number | null; signal: NodeJS.Signals | null }> {
  return await new Promise((resolve, reject) => {
    const timeout = setTimeout(() => {
      reject(new Error("child did not close before timeout"));
    }, timeoutMs);
    child.once("close", (code, signal) => {
      clearTimeout(timeout);
      resolve({ code, signal });
    });
  });
}

describe("write-cli-startup-metadata", () => {
  const { createTempDir } = createScriptTestHarness();

  it("renders source root help without blocking sibling child events", async () => {
    const child = createSpawnTextChild();
    const spawnMock = vi.mocked(spawn);
    spawnMock.mockImplementationOnce(() => child as unknown as ReturnType<typeof spawn>);
    let siblingEventObserved = false;
    const siblingEvent = new Promise<void>((resolve) => {
      setImmediate(() => {
        siblingEventObserved = true;
        resolve();
      });
    });

    const render = testing.renderSourceRootHelpText();
    child.stdout.write("Usage: openclaw\n");
    setImmediate(() => {
      child.emit("close", 0, null);
    });

    await siblingEvent;
    expect(siblingEventObserved).toBe(true);
    await expect(render).resolves.toBe("Usage: openclaw\n");
    expect(spawnMock).toHaveBeenCalledOnce();
    expect(spawnMock.mock.calls[0]?.[1]).toEqual([
      "--import",
      "tsx",
      "--input-type=module",
      "--eval",
      expect.any(String),
    ]);
    expect(spawnMock.mock.calls[0]?.[2]).toMatchObject({
      detached: process.platform !== "win32",
      stdio: ["ignore", "pipe", "pipe"],
    });
  });

  it("fails command help rendering when captured output exceeds the byte limit", async () => {
    await expect(
      testing.spawnText(["--eval", "process.stdout.write('x'.repeat(2048))"], {
        cwd: process.cwd(),
        env: process.env,
        failureMessage: "render failed",
        killGraceMs: 25,
        maxOutputBytes: 1024,
        timeoutMs: 5_000,
      }),
    ).rejects.toThrow("render failed: output exceeded 1024 bytes");
  });

  it.each(["stdout", "stderr"] as const)(
    "fails command help rendering when %s emits a stream error",
    async (streamName) => {
      const child = createSpawnTextChild();
      const spawnProcess = vi.fn(() => child as unknown as ReturnType<typeof spawn>);
      const streamError = new Error(`${streamName} pipe failed`);

      const render = testing.spawnText(["--help"], {
        cwd: process.cwd(),
        env: process.env,
        failureMessage: "render failed",
        killGraceMs: 25,
        maxOutputBytes: 1024,
        spawnProcess: spawnProcess as typeof spawn,
        timeoutMs: 5_000,
      });

      child[streamName].emit("error", streamError);
      child.emit("close", null, "SIGTERM");

      await expect(render).rejects.toMatchObject({
        message: expect.stringContaining(
          `render failed: ${streamName} read error: ${streamName} pipe failed`,
        ),
        cause: streamError,
      });
      expect(child.kill).toHaveBeenCalledWith("SIGTERM");
    },
  );

  it("preserves an output-limit failure when shutdown also errors a stream", async () => {
    const child = createSpawnTextChild();
    const spawnProcess = vi.fn(() => child as unknown as ReturnType<typeof spawn>);
    const render = testing.spawnText(["--help"], {
      cwd: process.cwd(),
      env: process.env,
      failureMessage: "render failed",
      killGraceMs: 25,
      maxOutputBytes: 5,
      spawnProcess: spawnProcess as typeof spawn,
      timeoutMs: 5_000,
    });

    child.stdout.emit("data", "123456");
    child.stdout.emit("error", new Error("pipe closed during shutdown"));
    child.emit("close", null, "SIGTERM");

    await expect(render).rejects.toThrow("render failed: output exceeded 5 bytes");
  });

  it("aborts and drains the default command batch before removing shared state", async () => {
    const actualSpawn = (
      await vi.importActual<typeof import("node:child_process")>("node:child_process")
    ).spawn;
    const spawnMock = vi.mocked(spawn);
    const tempRoot = createTempDir("openclaw-startup-metadata-batch-failure-");
    const distDir = path.join(tempRoot, "dist");
    const extensionsDir = path.join(tempRoot, "extensions");
    const outputPath = path.join(distDir, "cli-startup-metadata.json");
    const events: string[] = [];
    const children: Array<ReturnType<typeof createSpawnTextChild> & { commandName: string }> = [];
    const realRmSync = fs.rmSync.bind(fs);
    const removeState = vi.spyOn(fs, "rmSync").mockImplementation((target, options) => {
      events.push("cleanup");
      return realRmSync(target, options);
    });

    writeStartupMetadataSourceSignatureFixture(tempRoot);
    writeFixtureFile(distDir, "root-help-fixture.js", "export function outputRootHelp() {}\n");

    spawnMock.mockImplementation((_command, args) => {
      const commandName = String(args[1]);
      const child = Object.assign(createSpawnTextChild(), { commandName });
      child.kill.mockImplementation((signal) => {
        events.push(`kill:${commandName}:${signal}`);
        queueMicrotask(() => {
          events.push(`close:${commandName}`);
          child.emit("close", null, signal);
        });
        return true;
      });
      children.push(child);
      return child as unknown as ReturnType<typeof spawn>;
    });

    try {
      const writePromise = testing.writeCliStartupMetadata({
        distDir,
        outputPath,
        extensionsDir,
        sourceRootDir: tempRoot,
        renderBundledRootHelpText: async () => "Usage: openclaw\n",
      });
      const deadline = Date.now() + 1_000;
      while (children.length < COMMAND_HELP_RENDER_CONCURRENCY && Date.now() < deadline) {
        await new Promise((resolve) => setImmediate(resolve));
      }
      expect(children.map((child) => child.commandName)).toEqual(
        DEFAULT_COMMAND_HELP_NAMES.slice(0, COMMAND_HELP_RENDER_CONCURRENCY),
      );

      const browser = children[0];
      expect(browser).toBeDefined();
      browser?.stderr.write("browser renderer failed\n");
      browser?.emit("close", 7, null);

      const error = await writePromise.then(
        () => undefined,
        (reason: unknown) => reason,
      );
      expect(error).toBeInstanceOf(Error);
      expect((error as Error).message).toContain("Failed to render source browser help");
      expect((error as Error).message).toContain("browser renderer failed");
      expect((error as Error).message).toMatch(/browser renderer failed \(elapsed \d+ms\)/u);
      expect(children.map((child) => child.commandName)).not.toContain("tasks");
      for (const child of children.slice(1)) {
        expect(child.kill).toHaveBeenCalledWith("SIGTERM");
        expect(events).toContain(`close:${child.commandName}`);
      }
      expect(events.at(-1)).toBe("cleanup");
      expect(existsSync(outputPath)).toBe(false);
    } finally {
      removeState.mockRestore();
      spawnMock.mockImplementation(actualSpawn);
    }
  });

  it.runIf(process.platform !== "win32")(
    "preserves shared state when a canceled process group cannot be proven dead",
    async () => {
      const tempRoot = createTempDir("openclaw-startup-metadata-undrained-tree-");
      const distDir = path.join(tempRoot, "dist");
      const extensionsDir = path.join(tempRoot, "extensions");
      const outputPath = path.join(distDir, "cli-startup-metadata.json");
      const child = Object.assign(createSpawnTextChild(), { pid: 123 });
      const realProcessKill = process.kill.bind(process);
      const processKill = vi.spyOn(process, "kill").mockImplementation((pid, signal) => {
        if (pid === -123) {
          return true;
        }
        return realProcessKill(pid, signal);
      });
      let renderStateDir = "";

      writeStartupMetadataSourceSignatureFixture(tempRoot);
      writeFixtureFile(distDir, "root-help-fixture.js", "export function outputRootHelp() {}\n");

      try {
        const writePromise = testing.writeCliStartupMetadata({
          distDir,
          outputPath,
          extensionsDir,
          sourceRootDir: tempRoot,
          renderBundledRootHelpText: async () => "Usage: openclaw\n",
          renderSourceBrowserHelpText: (renderContext, taskContext) => {
            renderStateDir = renderContext.env?.OPENCLAW_STATE_DIR ?? "";
            if (!taskContext) {
              throw new Error("missing render task context");
            }
            return testing.spawnText(["openclaw.mjs", "browser", "--help"], {
              cwd: tempRoot,
              env: process.env,
              failureMessage: "browser render failed",
              killGraceMs: 10,
              maxOutputBytes: 1024,
              onTerminalFailure: taskContext.reportFailure,
              signal: taskContext.signal,
              spawnProcess: (() => child as unknown as ReturnType<typeof spawn>) as typeof spawn,
              timeoutMs: 5_000,
            });
          },
          renderSourceSecretsHelpText: () => "Usage: openclaw secrets\n",
          renderSourceNodesHelpText: () => "Usage: openclaw nodes\n",
          renderSourceSubcommandHelpTextRecord: () => ({
            doctor: "Usage: openclaw doctor\n",
            gateway: "Usage: openclaw gateway\n",
            models: "Usage: openclaw models\n",
            plugins: "Usage: openclaw plugins\n",
            sessions: "Usage: openclaw sessions\n",
            tasks: "Usage: openclaw tasks\n",
          }),
        });
        await new Promise((resolve) => setImmediate(resolve));
        child.stderr.write("primary browser failure\n");
        child.emit("close", 7, null);

        const error = await writePromise.then(
          () => undefined,
          (reason: unknown) => reason,
        );
        expect(error).toBeInstanceOf(Error);
        expect((error as Error).message).toContain("primary browser failure");
        expect((error as Error).message).toContain(
          `Preserved CLI startup metadata render state: ${renderStateDir}`,
        );
        expect(error).toMatchObject({
          preserveRenderState: true,
          processTreeCleanupFailure: {
            code: "EPROCESSGROUP_CLEANUP_FAILED",
          },
        });
        expect(existsSync(renderStateDir)).toBe(true);
        expect(existsSync(outputPath)).toBe(false);
      } finally {
        processKill.mockRestore();
        if (renderStateDir) {
          fs.rmSync(renderStateDir, { force: true, recursive: true });
        }
      }
    },
  );

  it.runIf(process.platform !== "win32")(
    "cancels a default-batch sibling process tree after another command fails",
    async () => {
      const actualSpawn = (
        await vi.importActual<typeof import("node:child_process")>("node:child_process")
      ).spawn;
      const spawnMock = vi.mocked(spawn);
      const tempRoot = createTempDir("openclaw-startup-metadata-batch-tree-");
      const distDir = path.join(tempRoot, "dist");
      const extensionsDir = path.join(tempRoot, "extensions");
      const outputPath = path.join(distDir, "cli-startup-metadata.json");
      const grandchildPidPath = path.join(tempRoot, "grandchild.pid");
      const startedCommands: string[] = [];
      const startedChildren: Array<ReturnType<typeof spawn>> = [];
      let grandchildPid = 0;

      writeStartupMetadataSourceSignatureFixture(tempRoot);
      writeFixtureFile(distDir, "root-help-fixture.js", "export function outputRootHelp() {}\n");

      const failingScript = [
        "const { existsSync } = await import('node:fs');",
        `const marker = ${JSON.stringify(grandchildPidPath)};`,
        "const timer = setInterval(() => {",
        "  if (!existsSync(marker)) return;",
        "  clearInterval(timer);",
        "  process.stderr.write('browser sentinel failure\\n', () => process.exit(9));",
        "}, 5);",
      ].join("\n");
      const grandchildScript = [
        "process.on('SIGTERM', () => setTimeout(() => process.exit(0), 50));",
        "setInterval(() => {}, 1000);",
      ].join("\n");
      const siblingScript = [
        "const { spawn } = await import('node:child_process');",
        "const { writeFileSync } = await import('node:fs');",
        `const grandchild = spawn(process.execPath, ["--eval", ${JSON.stringify(grandchildScript)}], { stdio: "ignore" });`,
        `writeFileSync(${JSON.stringify(grandchildPidPath)}, String(grandchild.pid));`,
        "process.on('SIGTERM', () => setTimeout(() => process.exit(0), 100));",
        "setInterval(() => {}, 1000);",
      ].join("\n");
      const idleScript = [
        "process.on('SIGTERM', () => process.exit(0));",
        "setInterval(() => {}, 1000);",
      ].join("\n");

      spawnMock.mockImplementation((_command, args, options) => {
        const commandName = String(args[1]);
        startedCommands.push(commandName);
        const script =
          commandName === "browser"
            ? failingScript
            : commandName === "secrets"
              ? siblingScript
              : idleScript;
        const child = actualSpawn(
          process.execPath,
          ["--input-type=module", "--eval", script],
          options,
        );
        startedChildren.push(child);
        return child;
      });

      try {
        const startedAt = Date.now();
        const error = await testing
          .writeCliStartupMetadata({
            distDir,
            outputPath,
            extensionsDir,
            sourceRootDir: tempRoot,
            renderBundledRootHelpText: async () => "Usage: openclaw\n",
          })
          .then(
            () => undefined,
            (reason: unknown) => reason,
          );

        grandchildPid = await waitForPidFile(grandchildPidPath, LOAD_SENSITIVE_PROCESS_TIMEOUT_MS);
        expect(error).toBeInstanceOf(Error);
        expect((error as Error).message).toContain("browser sentinel failure");
        expect(Date.now() - startedAt).toBeLessThan(LOAD_SENSITIVE_PROCESS_TIMEOUT_MS);
        expect(startedCommands).toHaveLength(COMMAND_HELP_RENDER_CONCURRENCY);
        expect(startedCommands).not.toContain("tasks");
        await waitForProcessExit(grandchildPid);
        expect(existsSync(outputPath)).toBe(false);
      } finally {
        spawnMock.mockImplementation(actualSpawn);
        for (const child of startedChildren) {
          if (child.pid && processIsAlive(child.pid)) {
            try {
              process.kill(-child.pid, "SIGKILL");
            } catch {}
          }
        }
        if (grandchildPid > 0 && processIsAlive(grandchildPid)) {
          try {
            process.kill(grandchildPid, "SIGKILL");
          } catch {}
        }
      }
    },
  );

  it("signals Windows command help render process trees with taskkill", () => {
    const childKill = vi.fn(() => true);
    const runTaskkill = vi.fn(() => ({ error: undefined, status: 0 }));

    testing.signalCliStartupMetadataProcessTree({ pid: 123, kill: childKill }, "SIGTERM", {
      platform: "win32",
      runTaskkill,
    });
    expect(runTaskkill).toHaveBeenNthCalledWith(1, expectedTaskkillPath(), ["/PID", "123", "/T"], {
      stdio: "ignore",
    });

    testing.signalCliStartupMetadataProcessTree({ pid: 123, kill: childKill }, "SIGKILL", {
      platform: "win32",
      runTaskkill,
    });
    expect(runTaskkill).toHaveBeenNthCalledWith(
      2,
      expectedTaskkillPath(),
      ["/PID", "123", "/T", "/F"],
      {
        stdio: "ignore",
      },
    );
    expect(childKill).not.toHaveBeenCalled();
  });

  it("force-kills Windows command help render process trees when graceful taskkill fails", () => {
    const childKill = vi.fn(() => true);
    const runTaskkill = vi
      .fn()
      .mockReturnValueOnce({ error: undefined, status: 1 })
      .mockReturnValueOnce({ error: undefined, status: 0 });

    testing.signalCliStartupMetadataProcessTree({ pid: 123, kill: childKill }, "SIGTERM", {
      platform: "win32",
      runTaskkill,
    });

    expect(runTaskkill).toHaveBeenNthCalledWith(1, expectedTaskkillPath(), ["/PID", "123", "/T"], {
      stdio: "ignore",
    });
    expect(runTaskkill).toHaveBeenNthCalledWith(
      2,
      expectedTaskkillPath(),
      ["/PID", "123", "/T", "/F"],
      {
        stdio: "ignore",
      },
    );
    expect(childKill).not.toHaveBeenCalled();
  });

  it.runIf(process.platform !== "win32")(
    "kills descendant processes when command help rendering times out",
    async () => {
      const tempRoot = createTempDir("openclaw-startup-metadata-timeout-");
      const markerPath = path.join(tempRoot, "grandchild.pid");
      const grandchildScript = [
        "process.on('SIGTERM', () => {});",
        "setInterval(() => {}, 1000);",
      ].join("\n");
      const parentScript = [
        "const { spawn } = await import('node:child_process');",
        "const { writeFileSync } = await import('node:fs');",
        `const grandchild = spawn(process.execPath, ["--eval", ${JSON.stringify(grandchildScript)}], { stdio: "ignore" });`,
        `writeFileSync(${JSON.stringify(markerPath)}, String(grandchild.pid));`,
        "process.on('SIGTERM', () => {});",
        "setInterval(() => {}, 1000);",
      ].join("\n");

      await expect(
        testing.spawnText(["--input-type=module", "--eval", parentScript], {
          cwd: tempRoot,
          env: process.env,
          failureMessage: "render failed",
          killGraceMs: 25,
          maxOutputBytes: 1024,
          timeoutMs: 500,
        }),
      ).rejects.toThrow("render failed: timed out after 500ms");

      const grandchildPid = await waitForPidFile(markerPath, LOAD_SENSITIVE_PROCESS_TIMEOUT_MS);
      await waitForProcessExit(grandchildPid);
    },
  );

  it.runIf(process.platform !== "win32")(
    "drains descendants when a command leader exits nonzero",
    async () => {
      const tempRoot = createTempDir("openclaw-startup-metadata-nonzero-tree-");
      const markerPath = path.join(tempRoot, "grandchild.pid");
      const grandchildScript = [
        "process.on('SIGTERM', () => {});",
        "setInterval(() => {}, 1000);",
      ].join("\n");
      const parentScript = [
        "const { spawn } = await import('node:child_process');",
        "const { writeFileSync } = await import('node:fs');",
        `const grandchild = spawn(process.execPath, ["--eval", ${JSON.stringify(grandchildScript)}], { stdio: "ignore" });`,
        `writeFileSync(${JSON.stringify(markerPath)}, String(grandchild.pid));`,
        "process.stderr.write('leader failed\\n', () => process.exit(7));",
      ].join("\n");

      await expect(
        testing.spawnText(["--input-type=module", "--eval", parentScript], {
          cwd: tempRoot,
          env: process.env,
          failureMessage: "render failed",
          killGraceMs: 25,
          maxOutputBytes: 1024,
          timeoutMs: 5_000,
        }),
      ).rejects.toThrow(/render failed: leader failed.*elapsed \d+ms/u);

      const grandchildPid = Number(readFileSync(markerPath, "utf8"));
      await waitForProcessExit(grandchildPid);
    },
  );

  it.runIf(process.platform !== "win32")(
    "waits for all command help descendants before re-raising parent signals",
    async () => {
      const tempRoot = createTempDir("openclaw-startup-metadata-signal-");
      const fastCommandPath = path.join(tempRoot, "fast-command.mjs");
      const fastReadyPath = path.join(tempRoot, "fast-ready");
      const commandPath = path.join(tempRoot, "command.mjs");
      const runnerPath = path.join(tempRoot, "runner.mjs");
      const grandchildPidPath = path.join(tempRoot, "grandchild.pid");
      const renderStatePath = path.join(tempRoot, "render-state.txt");
      const distDir = path.join(tempRoot, "dist");
      const outputPath = path.join(distDir, "cli-startup-metadata.json");
      const grandchildScript = [
        "process.on('SIGTERM', () => {});",
        "setInterval(() => {}, 1000);",
      ].join("\n");
      writeFixtureFile(
        tempRoot,
        "fast-command.mjs",
        [
          "import { writeFileSync } from 'node:fs';",
          `writeFileSync(${JSON.stringify(fastReadyPath)}, "ready");`,
          "process.on('SIGTERM', () => process.exit(0));",
          "setInterval(() => {}, 1000);",
        ].join("\n"),
      );
      writeFixtureFile(
        tempRoot,
        "command.mjs",
        [
          "import { spawn } from 'node:child_process';",
          "import { writeFileSync } from 'node:fs';",
          `const grandchild = spawn(process.execPath, ["--eval", ${JSON.stringify(
            grandchildScript,
          )}], { stdio: "ignore" });`,
          `writeFileSync(${JSON.stringify(grandchildPidPath)}, String(grandchild.pid));`,
          "process.on('SIGTERM', () => process.exit(0));",
          "setInterval(() => {}, 1000);",
        ].join("\n"),
      );
      writeStartupMetadataSourceSignatureFixture(tempRoot);
      writeFixtureFile(distDir, "root-help-fixture.js", "export function outputRootHelp() {}\n");
      writeFixtureFile(
        tempRoot,
        "runner.mjs",
        [
          `const { testing } = await import(${JSON.stringify(
            pathToFileURL(path.resolve("scripts/write-cli-startup-metadata.ts")).href,
          )});`,
          "const { writeFileSync } = await import('node:fs');",
          "const renderCommand = (commandPath, failureMessage) => (context, taskContext) => {",
          "  if (!taskContext) throw new Error('missing render task context');",
          `  writeFileSync(${JSON.stringify(renderStatePath)}, context.env.OPENCLAW_STATE_DIR);`,
          "  return testing.spawnText([commandPath], {",
          `    cwd: ${JSON.stringify(tempRoot)},`,
          "    env: process.env,",
          "    failureMessage,",
          "    killGraceMs: 100,",
          "    maxOutputBytes: 1024,",
          "    onTerminalFailure: taskContext.reportFailure,",
          "    signal: taskContext.signal,",
          "    timeoutMs: 30_000,",
          "  });",
          "};",
          "await testing.writeCliStartupMetadata({",
          `  distDir: ${JSON.stringify(distDir)},`,
          `  outputPath: ${JSON.stringify(outputPath)},`,
          `  extensionsDir: ${JSON.stringify(path.join(tempRoot, "extensions"))},`,
          `  sourceRootDir: ${JSON.stringify(tempRoot)},`,
          "  renderBundledRootHelpText: async () => 'Usage: openclaw\\n',",
          `  renderSourceBrowserHelpText: renderCommand(${JSON.stringify(fastCommandPath)}, 'fast render failed'),`,
          `  renderSourceSecretsHelpText: renderCommand(${JSON.stringify(commandPath)}, 'render failed'),`,
          "  renderSourceNodesHelpText: () => 'Usage: openclaw nodes\\n',",
          "  renderSourceSubcommandHelpTextRecord: () => ({",
          "    doctor: 'Usage: openclaw doctor\\n', gateway: 'Usage: openclaw gateway\\n',",
          "    models: 'Usage: openclaw models\\n', plugins: 'Usage: openclaw plugins\\n',",
          "    sessions: 'Usage: openclaw sessions\\n', tasks: 'Usage: openclaw tasks\\n',",
          "  }),",
          "});",
        ].join("\n"),
      );

      const runner = spawn(process.execPath, ["--import", "tsx", runnerPath], {
        cwd: process.cwd(),
        stdio: "ignore",
      });
      let grandchildPid = 0;

      try {
        const deadline = Date.now() + LOAD_SENSITIVE_PROCESS_TIMEOUT_MS;
        grandchildPid = await waitForPidFile(grandchildPidPath, LOAD_SENSITIVE_PROCESS_TIMEOUT_MS);
        while (Date.now() < deadline) {
          let fastReady = false;
          try {
            fastReady = readFileSync(fastReadyPath, "utf8") === "ready";
          } catch {}
          if (fastReady && grandchildPid > 0 && processIsAlive(grandchildPid)) {
            break;
          }
          await new Promise((resolve) => {
            setTimeout(resolve, 10);
          });
        }
        expect(readFileSync(fastReadyPath, "utf8")).toBe("ready");
        expect(grandchildPid).toBeGreaterThan(0);
        expect(processIsAlive(grandchildPid)).toBe(true);

        runner.kill("SIGTERM");

        await expect(waitForChildClose(runner)).resolves.toEqual({
          code: null,
          signal: "SIGTERM",
        });
        await waitForProcessExit(grandchildPid);
        const renderStateDir = readFileSync(renderStatePath, "utf8");
        expect(existsSync(renderStateDir)).toBe(false);
      } finally {
        if (runner.pid && processIsAlive(runner.pid)) {
          runner.kill("SIGKILL");
        }
        if (grandchildPid > 0 && processIsAlive(grandchildPid)) {
          process.kill(grandchildPid, "SIGKILL");
        }
      }
    },
  );

  it("writes startup metadata with populated root help text when dist falls back to source rendering", async () => {
    const tempRoot = createTempDir("openclaw-startup-metadata-");
    const distDir = path.join(tempRoot, "dist");
    const extensionsDir = path.join(tempRoot, "extensions");
    const outputPath = path.join(distDir, "cli-startup-metadata.json");

    mkdirSync(distDir, { recursive: true });
    mkdirSync(path.join(extensionsDir, "matrix"), { recursive: true });
    writeFileSync(
      path.join(extensionsDir, "matrix", "package.json"),
      JSON.stringify({
        openclaw: {
          channel: {
            id: "matrix",
            order: 120,
            label: "Matrix",
          },
        },
      }),
      "utf8",
    );

    await testing.writeCliStartupMetadata({
      distDir,
      outputPath,
      extensionsDir,
      renderSourceRootHelpText: () => "Usage: openclaw\n",
      renderSourceBrowserHelpText: () => "Usage: openclaw browser\n",
      renderSourceSecretsHelpText: () => "Usage: openclaw secrets\n",
      renderSourceNodesHelpText: () => "Usage: openclaw nodes\n",
      renderSourceSubcommandHelpTextRecord: () => ({
        doctor: "Usage: openclaw doctor\n",
        gateway: "Usage: openclaw gateway\n",
        models: "Usage: openclaw models\n",
        plugins: "Usage: openclaw plugins\n",
        sessions: "Usage: openclaw sessions\n",
        tasks: "Usage: openclaw tasks\n",
      }),
    });

    const written = JSON.parse(readFileSync(outputPath, "utf8")) as {
      browserHelpText: string;
      channelOptions: string[];
      generatorSignature: string;
      nodesHelpText: string;
      rootHelpText: string;
      secretsHelpText: string;
      subcommandHelpText: {
        doctor: string;
        gateway: string;
        models: string;
        plugins: string;
        sessions: string;
        tasks: string;
      };
    };
    expect(written.channelOptions).toContain("matrix");
    expect(written.generatorSignature).toMatch(/^[a-f0-9]{40}$/u);
    expect(written.browserHelpText).toContain("Usage:");
    expect(written.browserHelpText).toContain("openclaw browser");
    expect(written.secretsHelpText).toContain("Usage:");
    expect(written.secretsHelpText).toContain("openclaw secrets");
    expect(written.nodesHelpText).toContain("Usage:");
    expect(written.nodesHelpText).toContain("openclaw nodes");
    expect(written.rootHelpText).toContain("Usage:");
    expect(written.rootHelpText).toContain("openclaw");
    expect(written.subcommandHelpText.doctor).toContain("openclaw doctor");
    expect(written.subcommandHelpText.gateway).toContain("openclaw gateway");
    expect(written.subcommandHelpText.models).toContain("openclaw models");
    expect(written.subcommandHelpText.plugins).toContain("openclaw plugins");
    expect(written.subcommandHelpText.sessions).toContain("openclaw sessions");
    expect(written.subcommandHelpText.tasks).toContain("openclaw tasks");
  });

  it("does not source-fallback a bundled root resource failure", async () => {
    const tempRoot = createTempDir("openclaw-startup-metadata-root-resource-failure-");
    const distDir = path.join(tempRoot, "dist");
    const extensionsDir = path.join(tempRoot, "extensions");
    const outputPath = path.join(distDir, "cli-startup-metadata.json");
    const renderSourceRootHelpText = vi.fn(() => "Usage: source fallback\n");

    writeStartupMetadataSourceSignatureFixture(tempRoot);
    writeFixtureFile(distDir, "root-help-fixture.js", "export function outputRootHelp() {}\n");

    const error = await testing
      .writeCliStartupMetadata({
        distDir,
        outputPath,
        extensionsDir,
        sourceRootDir: tempRoot,
        renderBundledRootHelpText: async () => {
          throw Object.assign(new Error("bundled root timed out"), { code: "ETIMEDOUT" });
        },
        renderSourceRootHelpText,
        renderSourceBrowserHelpText: () => "Usage: openclaw browser\n",
        renderSourceSecretsHelpText: () => "Usage: openclaw secrets\n",
        renderSourceNodesHelpText: () => "Usage: openclaw nodes\n",
        renderSourceSubcommandHelpTextRecord: () => ({
          doctor: "Usage: openclaw doctor\n",
          gateway: "Usage: openclaw gateway\n",
          models: "Usage: openclaw models\n",
          plugins: "Usage: openclaw plugins\n",
          sessions: "Usage: openclaw sessions\n",
          tasks: "Usage: openclaw tasks\n",
        }),
      })
      .then(
        () => undefined,
        (reason: unknown) => reason,
      );

    expect(error).toBeInstanceOf(Error);
    expect((error as Error).message).toContain("bundled root timed out");
    expect(renderSourceRootHelpText).not.toHaveBeenCalled();
    expect(existsSync(outputPath)).toBe(false);
  });

  it("selects the root-help bundle that exports the renderer", async () => {
    const tempRoot = createTempDir("openclaw-startup-metadata-bundle-selection-");
    const distDir = path.join(tempRoot, "dist");
    const extensionsDir = path.join(tempRoot, "extensions");
    const outputPath = path.join(distDir, "cli-startup-metadata.json");
    const renderSourceRootHelpText = vi.fn(() => "Usage: source fallback\n");

    writeStartupMetadataSourceSignatureFixture(tempRoot);
    writeFixtureFile(tempRoot, "package.json", '{"type":"module"}\n');
    writeFixtureFile(
      distDir,
      "root-help-live-config-fixture.js",
      "async function loadRootHelpRenderOptionsForConfigSensitivePlugins() { return null; }\nexport { loadRootHelpRenderOptionsForConfigSensitivePlugins };\n",
    );
    writeFixtureFile(
      distDir,
      "root-help-renderer-fixture.js",
      "async function outputRootHelp() { process.stdout.write('Usage: bundled renderer\\n'); }\nexport { outputRootHelp };\n",
    );

    await testing.writeCliStartupMetadata({
      distDir,
      outputPath,
      extensionsDir,
      sourceRootDir: tempRoot,
      renderSourceRootHelpText,
      renderSourceBrowserHelpText: () => "Usage: openclaw browser\n",
      renderSourceSecretsHelpText: () => "Usage: openclaw secrets\n",
      renderSourceNodesHelpText: () => "Usage: openclaw nodes\n",
      renderSourceSubcommandHelpTextRecord: () => ({
        doctor: "Usage: openclaw doctor\n",
        gateway: "Usage: openclaw gateway\n",
        models: "Usage: openclaw models\n",
        plugins: "Usage: openclaw plugins\n",
        sessions: "Usage: openclaw sessions\n",
        tasks: "Usage: openclaw tasks\n",
      }),
    });

    const written = JSON.parse(readFileSync(outputPath, "utf8")) as {
      rootHelpText: string;
    };
    expect(written.rootHelpText).toBe("Usage: bundled renderer\n");
    expect(renderSourceRootHelpText).not.toHaveBeenCalled();
  });

  it("renders independent startup help snapshots concurrently", async () => {
    const tempRoot = createTempDir("openclaw-startup-metadata-concurrency-");
    const distDir = path.join(tempRoot, "dist");
    const extensionsDir = path.join(tempRoot, "extensions");
    const outputPath = path.join(distDir, "cli-startup-metadata.json");
    const started: string[] = [];
    const unblockers = new Map<string, () => void>();
    const expectedStarted = ["browser", "secrets", "nodes", "subcommands"];

    mkdirSync(distDir, { recursive: true });
    writeStartupMetadataSourceSignatureFixture(tempRoot);
    writeFixtureFile(distDir, "root-help-fixture.js", "export function outputRootHelp() {}\n");

    const renderAfterUnblock = (label: string, output: string): (() => Promise<string>) => {
      return async () => {
        started.push(label);
        await new Promise<void>((resolve) => {
          unblockers.set(label, resolve);
        });
        return output;
      };
    };

    const waitForAllStarted = async (): Promise<void> => {
      const deadline = Date.now() + 1_000;
      while (Date.now() < deadline) {
        if (expectedStarted.every((label) => started.includes(label))) {
          return;
        }
        await new Promise((resolve) => {
          setTimeout(resolve, 5);
        });
      }
      throw new Error(`startup help renderers did not start concurrently: ${started.join(", ")}`);
    };

    const writePromise = testing.writeCliStartupMetadata({
      distDir,
      outputPath,
      extensionsDir,
      sourceRootDir: tempRoot,
      renderBundledRootHelpText: async () => "Usage: openclaw\n",
      renderSourceBrowserHelpText: renderAfterUnblock("browser", "Usage: openclaw browser\n"),
      renderSourceSecretsHelpText: renderAfterUnblock("secrets", "Usage: openclaw secrets\n"),
      renderSourceNodesHelpText: renderAfterUnblock("nodes", "Usage: openclaw nodes\n"),
      renderSourceSubcommandHelpTextRecord: async () => {
        started.push("subcommands");
        await new Promise<void>((resolve) => {
          unblockers.set("subcommands", resolve);
        });
        return {
          doctor: "Usage: openclaw doctor\n",
          gateway: "Usage: openclaw gateway\n",
          models: "Usage: openclaw models\n",
          plugins: "Usage: openclaw plugins\n",
          sessions: "Usage: openclaw sessions\n",
          tasks: "Usage: openclaw tasks\n",
        };
      },
    });

    await waitForAllStarted();
    for (const label of expectedStarted) {
      unblockers.get(label)?.();
    }
    await writePromise;

    const written = JSON.parse(readFileSync(outputPath, "utf8")) as {
      browserHelpText: string;
      nodesHelpText: string;
      secretsHelpText: string;
    };
    expect(written.browserHelpText).toContain("openclaw browser");
    expect(written.secretsHelpText).toContain("openclaw secrets");
    expect(written.nodesHelpText).toContain("openclaw nodes");
  });

  it.each([
    { title: "after successful rendering", failRender: false },
    { title: "when rendering fails", failRender: true },
  ])("removes isolated root-help state $title", async ({ failRender }) => {
    const removeState = vi.spyOn(fs, "rmSync");
    const tempRoot = createTempDir("openclaw-startup-metadata-cleanup-");
    const distDir = path.join(tempRoot, "dist");
    const extensionsDir = path.join(tempRoot, "extensions");
    const outputPath = path.join(distDir, "cli-startup-metadata.json");
    let stateDir = "";
    let statePresentDuringSiblingRender = false;

    writeStartupMetadataSourceSignatureFixture(tempRoot);
    writeFixtureFile(distDir, "root-help-fixture.js", "export function outputRootHelp() {}\n");

    const writeMetadata = testing.writeCliStartupMetadata({
      distDir,
      outputPath,
      extensionsDir,
      sourceRootDir: tempRoot,
      renderBundledRootHelpText: async () => "Usage: openclaw\n",
      renderSourceBrowserHelpText: async (renderContext) => {
        stateDir = renderContext.env?.OPENCLAW_STATE_DIR ?? "";
        const sqliteDir = path.join(stateDir, "state");
        mkdirSync(sqliteDir, { recursive: true });
        for (const suffix of ["", "-shm", "-wal"]) {
          writeFileSync(path.join(sqliteDir, `openclaw.sqlite${suffix}`), "fixture", "utf8");
        }
        await new Promise((resolve) => setImmediate(resolve));
        if (failRender) {
          throw new Error("browser help failed");
        }
        return "Usage: openclaw browser\n";
      },
      renderSourceSecretsHelpText: async () => {
        await new Promise((resolve) => {
          setImmediate(resolve);
        });
        statePresentDuringSiblingRender = existsSync(stateDir);
        return "Usage: openclaw secrets\n";
      },
      renderSourceNodesHelpText: () => "Usage: openclaw nodes\n",
      renderSourceSubcommandHelpTextRecord: () => ({
        doctor: "Usage: openclaw doctor\n",
        gateway: "Usage: openclaw gateway\n",
        models: "Usage: openclaw models\n",
        plugins: "Usage: openclaw plugins\n",
        sessions: "Usage: openclaw sessions\n",
        tasks: "Usage: openclaw tasks\n",
      }),
    });

    if (failRender) {
      await expect(writeMetadata).rejects.toThrow("browser help failed");
    } else {
      await expect(writeMetadata).resolves.toBeUndefined();
    }
    expect(stateDir).not.toBe("");
    expect(statePresentDuringSiblingRender).toBe(true);
    expect(existsSync(stateDir)).toBe(false);
    expect(removeState).toHaveBeenCalledWith(stateDir, {
      force: true,
      recursive: true,
      maxRetries: 6,
      retryDelay: 25,
    });
    removeState.mockRestore();
  });

  it("does not let shared-state cleanup mask the primary render failure", async () => {
    const tempRoot = createTempDir("openclaw-startup-metadata-cleanup-failure-");
    const distDir = path.join(tempRoot, "dist");
    const extensionsDir = path.join(tempRoot, "extensions");
    const outputPath = path.join(distDir, "cli-startup-metadata.json");
    const cleanupFailure = new Error("cleanup failed");
    const realRmSync = fs.rmSync.bind(fs);
    let renderStateDir = "";
    const removeState = vi.spyOn(fs, "rmSync").mockImplementation((target, options) => {
      if (String(target) === renderStateDir) {
        throw cleanupFailure;
      }
      return realRmSync(target, options);
    });

    writeStartupMetadataSourceSignatureFixture(tempRoot);
    writeFixtureFile(distDir, "root-help-fixture.js", "export function outputRootHelp() {}\n");

    try {
      const error = await testing
        .writeCliStartupMetadata({
          distDir,
          outputPath,
          extensionsDir,
          sourceRootDir: tempRoot,
          renderBundledRootHelpText: async () => "Usage: openclaw\n",
          renderSourceBrowserHelpText: (renderContext) => {
            renderStateDir = renderContext.env?.OPENCLAW_STATE_DIR ?? "";
            throw new Error("primary browser failure");
          },
          renderSourceSecretsHelpText: () => "Usage: openclaw secrets\n",
          renderSourceNodesHelpText: () => "Usage: openclaw nodes\n",
          renderSourceSubcommandHelpTextRecord: () => ({
            doctor: "Usage: openclaw doctor\n",
            gateway: "Usage: openclaw gateway\n",
            models: "Usage: openclaw models\n",
            plugins: "Usage: openclaw plugins\n",
            sessions: "Usage: openclaw sessions\n",
            tasks: "Usage: openclaw tasks\n",
          }),
        })
        .then(
          () => undefined,
          (reason: unknown) => reason,
        );

      expect(error).toBeInstanceOf(Error);
      expect((error as Error).message).toContain("primary browser failure");
      expect(error).toMatchObject({ cleanupError: cleanupFailure });
    } finally {
      removeState.mockRestore();
      if (renderStateDir) {
        realRmSync(renderStateDir, { force: true, recursive: true });
      }
    }
  });

  it("regenerates nodes help when bundled canvas CLI help sources change", async () => {
    const tempRoot = createTempDir("openclaw-startup-metadata-signature-");
    const distDir = path.join(tempRoot, "dist");
    const extensionsDir = path.join(tempRoot, "extensions");
    const outputPath = path.join(distDir, "cli-startup-metadata.json");
    let nodesRenderCount = 0;

    writeStartupMetadataSourceSignatureFixture(tempRoot);
    writeFixtureFile(distDir, "root-help-fixture.js", "export function outputRootHelp() {}\n");

    const writeMetadata = async (): Promise<void> => {
      await testing.writeCliStartupMetadata({
        distDir,
        outputPath,
        extensionsDir,
        sourceRootDir: tempRoot,
        renderBundledRootHelpText: async () => "Usage: openclaw\n",
        renderSourceBrowserHelpText: () => "Usage: openclaw browser\n",
        renderSourceSecretsHelpText: () => "Usage: openclaw secrets\n",
        renderSourceNodesHelpText: () => {
          nodesRenderCount += 1;
          return `Usage: openclaw nodes ${nodesRenderCount}\n`;
        },
        renderSourceSubcommandHelpTextRecord: () => ({
          doctor: "Usage: openclaw doctor\n",
          gateway: "Usage: openclaw gateway\n",
          models: "Usage: openclaw models\n",
          plugins: "Usage: openclaw plugins\n",
          sessions: "Usage: openclaw sessions\n",
          tasks: "Usage: openclaw tasks\n",
        }),
      });
    };

    await writeMetadata();
    await writeMetadata();
    expect(nodesRenderCount).toBe(1);

    const staleGeneratorMetadata = JSON.parse(readFileSync(outputPath, "utf8")) as Record<
      string,
      unknown
    >;
    staleGeneratorMetadata.generatorSignature = "stale-generator";
    writeFileSync(outputPath, `${JSON.stringify(staleGeneratorMetadata, null, 2)}\n`, "utf8");

    await writeMetadata();
    expect(nodesRenderCount).toBe(2);

    writeFixtureFile(
      tempRoot,
      "extensions/canvas/src/cli.ts",
      "export const canvasCliHelp = 'canvas changed help';\n",
    );

    await writeMetadata();

    const written = JSON.parse(readFileSync(outputPath, "utf8")) as {
      nodesHelpText: string;
    };
    expect(nodesRenderCount).toBe(3);
    expect(written.nodesHelpText).toContain("openclaw nodes 3");
  });

  it("regenerates help when build version or commit changes", async () => {
    const tempRoot = createTempDir("openclaw-startup-metadata-build-identity-");
    const distDir = path.join(tempRoot, "dist");
    const extensionsDir = path.join(tempRoot, "extensions");
    const outputPath = path.join(distDir, "cli-startup-metadata.json");
    let renderCount = 0;
    let commandRenderCount = 0;

    const renderSubcommandHelp = () => {
      commandRenderCount += 1;
      const buildInfo = JSON.parse(readFileSync(path.join(distDir, "build-info.json"), "utf8")) as {
        commit: string;
        version: string;
      };
      const banner = `OpenClaw ${buildInfo.version} (${buildInfo.commit.slice(0, 7)})`;
      return {
        doctor: `${banner}\nUsage: openclaw doctor\n`,
        gateway: `${banner}\nUsage: openclaw gateway\n`,
        models: `${banner}\nUsage: openclaw models\n`,
        plugins: `${banner}\nUsage: openclaw plugins\n`,
        sessions: `${banner}\nUsage: openclaw sessions\n`,
        tasks: `${banner}\nUsage: openclaw tasks\n`,
      };
    };

    writeStartupMetadataSourceSignatureFixture(tempRoot);
    writeFixtureFile(distDir, "root-help-fixture.js", "export function outputRootHelp() {}\n");

    const writeMetadata = async (): Promise<void> => {
      await testing.writeCliStartupMetadata({
        distDir,
        outputPath,
        extensionsDir,
        sourceRootDir: tempRoot,
        renderBundledRootHelpText: async () => {
          renderCount += 1;
          return `Usage: openclaw ${renderCount}\n`;
        },
        renderSourceBrowserHelpText: () => {
          commandRenderCount += 1;
          return "Usage: openclaw browser\n";
        },
        renderSourceSecretsHelpText: () => {
          commandRenderCount += 1;
          return "Usage: openclaw secrets\n";
        },
        renderSourceNodesHelpText: () => {
          commandRenderCount += 1;
          return "Usage: openclaw nodes\n";
        },
        renderSourceSubcommandHelpTextRecord: renderSubcommandHelp,
      });
    };

    writeFixtureFile(
      distDir,
      "build-info.json",
      JSON.stringify({ version: "2026.7.2", commit: "a".repeat(40) }),
    );
    await writeMetadata();
    await writeMetadata();
    expect(renderCount).toBe(1);
    expect(commandRenderCount).toBe(4);
    expect(readFileSync(outputPath, "utf8")).toContain("OpenClaw 2026.7.2 (aaaaaaa)");

    writeFixtureFile(
      distDir,
      "build-info.json",
      JSON.stringify({ version: "2026.7.2", commit: "b".repeat(40) }),
    );
    await writeMetadata();
    expect(renderCount).toBe(2);
    expect(commandRenderCount).toBe(8);
    expect(readFileSync(outputPath, "utf8")).toContain("OpenClaw 2026.7.2 (bbbbbbb)");

    writeFixtureFile(
      distDir,
      "build-info.json",
      JSON.stringify({ version: "2026.7.3", commit: "b".repeat(40) }),
    );
    await writeMetadata();
    expect(renderCount).toBe(3);
    expect(commandRenderCount).toBe(12);
    const written = JSON.parse(readFileSync(outputPath, "utf8")) as {
      subcommandHelpText: { models: string };
    };
    expect(written.subcommandHelpText.models).toContain("OpenClaw 2026.7.3 (bbbbbbb)");
  });
});
