fix: keep installer doctor non-interactive

This commit is contained in:
Peter Steinberger
2026-04-08 08:26:58 +01:00
parent b328c66115
commit 928a9e4915
7 changed files with 38 additions and 10 deletions

View File

@@ -56,7 +56,7 @@ else
fi
echo "==> Run official installer one-liner"
curl -fsSL "$INSTALL_URL" | bash
curl -fsSL "$INSTALL_URL" | bash -s -- --no-prompt
echo "==> Verify installed version"
if [[ -n "${OPENCLAW_INSTALL_LATEST_OUT:-}" ]]; then

View File

@@ -2466,15 +2466,13 @@ main() {
return 0
fi
local -a doctor_args=()
if [[ "$NO_ONBOARD" == "1" ]]; then
if "$claw" doctor --help 2>/dev/null | grep -q -- "--non-interactive"; then
doctor_args+=("--non-interactive")
fi
if [[ "$NO_ONBOARD" == "1" || "$NO_PROMPT" == "1" ]]; then
doctor_args+=("--non-interactive")
fi
ui_info "Running openclaw doctor"
local doctor_ok=0
if (( ${#doctor_args[@]} )); then
OPENCLAW_UPDATE_IN_PROGRESS=1 "$claw" doctor "${doctor_args[@]}" </dev/tty && doctor_ok=1
OPENCLAW_UPDATE_IN_PROGRESS=1 "$claw" doctor "${doctor_args[@]}" </dev/null && doctor_ok=1
else
OPENCLAW_UPDATE_IN_PROGRESS=1 "$claw" doctor </dev/tty && doctor_ok=1
fi

View File

@@ -31,6 +31,7 @@ docker run --rm -t \
-e OPENCLAW_INSTALL_SMOKE_PREVIOUS="${OPENCLAW_INSTALL_SMOKE_PREVIOUS:-}" \
-e OPENCLAW_INSTALL_SMOKE_SKIP_PREVIOUS="${OPENCLAW_INSTALL_SMOKE_SKIP_PREVIOUS:-0}" \
-e OPENCLAW_NO_ONBOARD=1 \
-e OPENCLAW_NO_PROMPT=1 \
-e DEBIAN_FRONTEND=noninteractive \
"$SMOKE_IMAGE"
@@ -58,6 +59,7 @@ else
-e OPENCLAW_INSTALL_METHOD=npm \
-e OPENCLAW_INSTALL_EXPECT_VERSION="$LATEST_VERSION" \
-e OPENCLAW_NO_ONBOARD=1 \
-e OPENCLAW_NO_PROMPT=1 \
-e DEBIAN_FRONTEND=noninteractive \
"$NONROOT_IMAGE"
fi
@@ -78,5 +80,6 @@ docker run --rm -t \
-e OPENCLAW_INSTALL_URL="$INSTALL_URL" \
-e OPENCLAW_INSTALL_CLI_URL="$CLI_INSTALL_URL" \
-e OPENCLAW_NO_ONBOARD=1 \
-e OPENCLAW_NO_PROMPT=1 \
-e DEBIAN_FRONTEND=noninteractive \
"$NONROOT_IMAGE" -lc "curl -fsSL \"$CLI_INSTALL_URL\" | bash -s -- --set-npm-prefix --no-onboard"

View File

@@ -9,7 +9,7 @@ import {
describe("command-registration-policy", () => {
it("matches primary command registration policy", () => {
expect(shouldRegisterPrimaryCommandOnly(["node", "openclaw", "status"])).toBe(true);
expect(shouldRegisterPrimaryCommandOnly(["node", "openclaw", "status", "--help"])).toBe(false);
expect(shouldRegisterPrimaryCommandOnly(["node", "openclaw", "status", "--help"])).toBe(true);
expect(shouldRegisterPrimaryCommandOnly(["node", "openclaw", "-V"])).toBe(false);
expect(shouldRegisterPrimaryCommandOnly(["node", "openclaw", "acp", "-v"])).toBe(true);
});
@@ -43,7 +43,7 @@ describe("command-registration-policy", () => {
expect(shouldEagerRegisterSubcommands({ OPENCLAW_DISABLE_LAZY_SUBCOMMANDS: "0" })).toBe(false);
expect(shouldRegisterPrimarySubcommandOnly(["node", "openclaw", "acp"], {})).toBe(true);
expect(shouldRegisterPrimarySubcommandOnly(["node", "openclaw", "acp", "--help"], {})).toBe(
false,
true,
);
expect(
shouldRegisterPrimarySubcommandOnly(["node", "openclaw", "acp"], {

View File

@@ -2,7 +2,8 @@ import { isTruthyEnvValue } from "../infra/env.js";
import { resolveCliArgvInvocation } from "./argv-invocation.js";
export function shouldRegisterPrimaryCommandOnly(argv: string[]): boolean {
return !resolveCliArgvInvocation(argv).hasHelpOrVersion;
const invocation = resolveCliArgvInvocation(argv);
return invocation.primary !== null || !invocation.hasHelpOrVersion;
}
export function shouldSkipPluginCommandRegistration(params: {

View File

@@ -109,10 +109,17 @@ describe("command-registry", () => {
expect(namesOf(program)).toEqual(["doctor"]);
});
it("does not narrow to the primary command when help is requested", () => {
it("narrows to the primary command when command help is requested", () => {
const program = createProgram();
registerCoreCliCommands(program, testProgramContext, ["node", "openclaw", "doctor", "--help"]);
expect(namesOf(program)).toEqual(["doctor"]);
});
it("keeps all placeholders for root help", () => {
const program = createProgram();
registerCoreCliCommands(program, testProgramContext, ["node", "openclaw", "--help"]);
const names = namesOf(program);
expect(names).toContain("doctor");
expect(names).toContain("status");

View File

@@ -190,6 +190,25 @@ describe("runCli exit behavior", () => {
process.exitCode = exitCode;
});
it("loads the real primary command before rendering command help", async () => {
buildProgramMock.mockReturnValueOnce({
commands: [{ name: () => "doctor" }],
parseAsync: vi.fn().mockResolvedValueOnce(undefined),
});
const ctx = { programVersion: "0.0.0-test" };
getProgramContextMock.mockReturnValueOnce(ctx as never);
await runCli(["node", "openclaw", "doctor", "--help"]);
expect(registerCoreCliByNameMock).toHaveBeenCalledWith(expect.anything(), ctx, "doctor", [
"node",
"openclaw",
"doctor",
"--help",
]);
expect(registerSubCliByNameMock).toHaveBeenCalledWith(expect.anything(), "doctor");
});
it("restores terminal state before uncaught CLI exits", async () => {
buildProgramMock.mockReturnValueOnce({
commands: [{ name: () => "status" }],