diff --git a/src/cli/gateway-cli/run.option-collisions.test.ts b/src/cli/gateway-cli/run.option-collisions.test.ts index df7b99cf866..c44fcb380f9 100644 --- a/src/cli/gateway-cli/run.option-collisions.test.ts +++ b/src/cli/gateway-cli/run.option-collisions.test.ts @@ -3,7 +3,6 @@ import fsPromises from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { Command } from "commander"; -import { withTempSecretFiles } from "../../test-utils/secret-file-fixture.js"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { createCliRuntimeCapture } from "../test-runtime-capture.js"; @@ -373,6 +372,18 @@ describe("gateway run option collisions", () => { expect(ensureDevGatewayConfig).toHaveBeenCalledWith({ reset: true }); }); + it("allows --dev --reset for explicit non-default legacy state/config paths", async () => { + vi.stubEnv("HOME", "/Users/test"); + vi.stubEnv("CLAWDBOT_STATE_DIR", "/tmp/custom-dev"); + vi.stubEnv("CLAWDBOT_CONFIG_PATH", "/tmp/custom-dev/openclaw.json"); + resolveStateDir.mockReturnValue("/tmp/custom-dev"); + resolveConfigPath.mockReturnValue("/tmp/custom-dev/openclaw.json"); + + await runGatewayCli(["gateway", "run", "--dev", "--reset", "--allow-unconfigured"]); + + expect(ensureDevGatewayConfig).toHaveBeenCalledWith({ reset: true }); + }); + it("hard-stops --dev --reset when state/config match non-dev profile defaults", async () => { vi.stubEnv("HOME", "/Users/test"); vi.stubEnv("OPENCLAW_PROFILE", "work"); @@ -389,6 +400,22 @@ describe("gateway run option collisions", () => { ); }); + it("hard-stops --dev --reset when legacy env resolves to non-dev profile defaults", async () => { + vi.stubEnv("HOME", "/Users/test"); + vi.stubEnv("OPENCLAW_PROFILE", "work"); + vi.stubEnv("CLAWDBOT_STATE_DIR", "/Users/test/.openclaw-work"); + vi.stubEnv("CLAWDBOT_CONFIG_PATH", "/Users/test/.openclaw-work/openclaw.json"); + resolveStateDir.mockReturnValue("/Users/test/.openclaw-work"); + resolveConfigPath.mockReturnValue("/Users/test/.openclaw-work/openclaw.json"); + + await expectGatewayExit(["gateway", "run", "--dev", "--reset"]); + + expect(ensureDevGatewayConfig).not.toHaveBeenCalled(); + expect(runtimeErrors.join("\n")).toContain( + "Refusing to run `gateway --dev --reset` because the reset target is not dev-isolated.", + ); + }); + it("hard-stops --dev --reset when state is profile-default but config is custom", async () => { vi.stubEnv("HOME", "/Users/test"); vi.stubEnv("OPENCLAW_PROFILE", "work"); diff --git a/src/cli/gateway-cli/run.ts b/src/cli/gateway-cli/run.ts index 4f0e8a3dba3..306f1c773e7 100644 --- a/src/cli/gateway-cli/run.ts +++ b/src/cli/gateway-cli/run.ts @@ -248,8 +248,12 @@ async function runGatewayCommand(opts: GatewayRunOpts) { : false; const stateTargetsProfileDefault = stateIsDefault || stateMatchesProfileDefault; const configTargetsProfileDefault = configIsDefault || configMatchesProfileDefault; - const hasStateOverride = Boolean(process.env.OPENCLAW_STATE_DIR?.trim()); - const hasConfigOverride = Boolean(process.env.OPENCLAW_CONFIG_PATH?.trim()); + const hasStateOverride = Boolean( + process.env.OPENCLAW_STATE_DIR?.trim() || process.env.CLAWDBOT_STATE_DIR?.trim(), + ); + const hasConfigOverride = Boolean( + process.env.OPENCLAW_CONFIG_PATH?.trim() || process.env.CLAWDBOT_CONFIG_PATH?.trim(), + ); const hasExplicitCustomTarget = hasStateOverride && !stateTargetsProfileDefault && diff --git a/src/cli/profile.test.ts b/src/cli/profile.test.ts index 14a66fd5b72..43e9f1d2991 100644 --- a/src/cli/profile.test.ts +++ b/src/cli/profile.test.ts @@ -145,6 +145,60 @@ describe("parseCliProfileArgs", () => { expect(res.argv).toEqual(["node", "openclaw", "docs", "aws", "--profile", "prod"]); }); + it("does not intercept --profile in acp client server args", () => { + const res = parseCliProfileArgs([ + "node", + "openclaw", + "acp", + "client", + "--server-args", + "--profile", + "work", + ]); + if (!res.ok) { + throw new Error(res.error); + } + expect(res.profile).toBeNull(); + expect(res.argv).toEqual([ + "node", + "openclaw", + "acp", + "client", + "--server-args", + "--profile", + "work", + ]); + }); + + it("preserves acp client server args after root option values", () => { + const res = parseCliProfileArgs([ + "node", + "openclaw", + "--log-level", + "debug", + "acp", + "client", + "--server-args", + "--profile", + "work", + ]); + if (!res.ok) { + throw new Error(res.error); + } + expect(res.profile).toBeNull(); + expect(res.argv).toEqual([ + "node", + "openclaw", + "--log-level", + "debug", + "acp", + "client", + "--server-args", + "--profile", + "work", + ]); + }); + it("keeps passthrough --profile when global --profile is set before terminator", () => { const res = parseCliProfileArgs([ "node", diff --git a/src/cli/profile.ts b/src/cli/profile.ts index 3d0ff84c5f9..b941b896a64 100644 --- a/src/cli/profile.ts +++ b/src/cli/profile.ts @@ -27,7 +27,7 @@ function takeValue( return { value: trimmed || null, consumedNext: Boolean(next) }; } -const ARBITRARY_ARG_COMMAND_PATHS = [["nodes", "run"], ["docs"]] as const; +const ARBITRARY_ARG_COMMAND_PATHS = [["nodes", "run"], ["docs"], ["acp", "client"]] as const; const ROOT_BOOLEAN_FLAGS = new Set(["--dev", "--no-color"]); const ROOT_VALUE_FLAGS = new Set(["--profile", "--log-level"]); diff --git a/src/commands/onboard-helpers.test.ts b/src/commands/onboard-helpers.test.ts index 1d28089ae63..ac1dd82275a 100644 --- a/src/commands/onboard-helpers.test.ts +++ b/src/commands/onboard-helpers.test.ts @@ -187,4 +187,17 @@ describe("resolveResetTargets", () => { expect(targets.credentialsPath).toBe(path.resolve("/tmp/custom-openclaw/credentials")); expect(targets.sessionsDir).toBe(path.resolve("/tmp/custom-openclaw/agents/main/sessions")); }); + + it("respects legacy CLAWDBOT_CONFIG_PATH override at runtime", () => { + const env = { + OPENCLAW_STATE_DIR: "/tmp/custom-openclaw", + CLAWDBOT_CONFIG_PATH: "/tmp/legacy/openclaw.json", + } as NodeJS.ProcessEnv; + const targets = resolveResetTargets(env); + + expect(targets.stateDir).toBe(path.resolve("/tmp/custom-openclaw")); + expect(targets.configPath).toBe(path.resolve("/tmp/legacy/openclaw.json")); + expect(targets.credentialsPath).toBe(path.resolve("/tmp/custom-openclaw/credentials")); + expect(targets.sessionsDir).toBe(path.resolve("/tmp/custom-openclaw/agents/main/sessions")); + }); }); diff --git a/src/commands/onboard-helpers.ts b/src/commands/onboard-helpers.ts index 67416429df1..a1fb7fd91bd 100644 --- a/src/commands/onboard-helpers.ts +++ b/src/commands/onboard-helpers.ts @@ -5,7 +5,7 @@ import { inspect } from "node:util"; import { cancel, isCancel } from "@clack/prompts"; import { DEFAULT_AGENT_WORKSPACE_DIR, ensureAgentWorkspace } from "../agents/workspace.js"; import type { OpenClawConfig } from "../config/config.js"; -import { resolveConfigPath, resolveStateDir } from "../config/config.js"; +import { resolveCanonicalConfigPath, resolveStateDir } from "../config/config.js"; import { resolveAgentModelPrimaryValue } from "../config/model-input.js"; import { resolveSessionTranscriptsDirForAgent } from "../config/sessions.js"; import { callGateway } from "../gateway/call.js"; @@ -335,7 +335,7 @@ export function resolveResetTargets( const stateDir = resolveStateDir(env); return { stateDir, - configPath: resolveConfigPath(env, stateDir), + configPath: resolveCanonicalConfigPath(env, stateDir), credentialsPath: path.join(stateDir, "credentials"), sessionsDir: resolveSessionTranscriptsDirForAgent(agentId, env), };