Onboard: reject invalid and remote-only tools profile usage

This commit is contained in:
Tak Hoffman
2026-03-05 21:15:32 -06:00
parent c7d1ab2a75
commit baec665cc5
4 changed files with 71 additions and 1 deletions

View File

@@ -178,6 +178,26 @@ describe("onboard (non-interactive): gateway and remote auth", () => {
});
}, 60_000);
it("rejects --tools-profile in remote mode", async () => {
await withStateDir("state-tools-profile-remote-", async (_stateDir) => {
await expect(
runNonInteractiveOnboarding(
{
nonInteractive: true,
mode: "remote",
remoteUrl: "wss://gateway.example.test",
toolsProfile: "coding",
authChoice: "skip",
skipSkills: true,
skipHealth: true,
installDaemon: false,
},
runtime,
),
).rejects.toThrow('--tools-profile is only supported when --mode is "local".');
});
}, 60_000);
it("uses OPENCLAW_GATEWAY_TOKEN when --gateway-token is omitted", async () => {
await withStateDir("state-env-token-", async (stateDir) => {
const envToken = "tok_env_fallback_123";

View File

@@ -27,6 +27,11 @@ export async function runNonInteractiveOnboarding(
runtime.exit(1);
return;
}
if (mode === "remote" && opts.toolsProfile !== undefined) {
runtime.error('--tools-profile is only supported when --mode is "local".');
runtime.exit(1);
return;
}
if (mode === "remote") {
await runNonInteractiveOnboardingRemote({ opts, runtime, baseConfig });

View File

@@ -156,4 +156,41 @@ describe("onboardCommand", () => {
expect(mocks.runInteractiveOnboarding).not.toHaveBeenCalled();
expect(mocks.runNonInteractiveOnboarding).not.toHaveBeenCalled();
});
it("fails fast for empty --tools-profile", async () => {
const runtime = makeRuntime();
await onboardCommand(
{
toolsProfile: "" as never,
},
runtime,
);
expect(runtime.error).toHaveBeenCalledWith(
'Invalid --tools-profile. Use "minimal", "coding", "messaging", or "full".',
);
expect(runtime.exit).toHaveBeenCalledWith(1);
expect(mocks.runInteractiveOnboarding).not.toHaveBeenCalled();
expect(mocks.runNonInteractiveOnboarding).not.toHaveBeenCalled();
});
it("fails fast for --tools-profile in remote mode", async () => {
const runtime = makeRuntime();
await onboardCommand(
{
mode: "remote",
toolsProfile: "coding",
},
runtime,
);
expect(runtime.error).toHaveBeenCalledWith(
'--tools-profile is only supported when --mode is "local".',
);
expect(runtime.exit).toHaveBeenCalledWith(1);
expect(mocks.runInteractiveOnboarding).not.toHaveBeenCalled();
expect(mocks.runNonInteractiveOnboarding).not.toHaveBeenCalled();
});
});

View File

@@ -46,11 +46,19 @@ export async function onboardCommand(opts: OnboardOptions, runtime: RuntimeEnv =
runtime.exit(1);
return;
}
if (normalizedOpts.toolsProfile && !VALID_TOOLS_PROFILES.has(normalizedOpts.toolsProfile)) {
if (
normalizedOpts.toolsProfile !== undefined &&
!VALID_TOOLS_PROFILES.has(normalizedOpts.toolsProfile)
) {
runtime.error('Invalid --tools-profile. Use "minimal", "coding", "messaging", or "full".');
runtime.exit(1);
return;
}
if (normalizedOpts.mode === "remote" && normalizedOpts.toolsProfile !== undefined) {
runtime.error('--tools-profile is only supported when --mode is "local".');
runtime.exit(1);
return;
}
if (normalizedOpts.resetScope && !VALID_RESET_SCOPES.has(normalizedOpts.resetScope)) {
runtime.error('Invalid --reset-scope. Use "config", "config+creds+sessions", or "full".');