Skip to content

Commit d3bfbde

Browse files
committed
fix(config): add actionable guidance for dmPolicy open allowFrom mismatch
1 parent 5a47525 commit d3bfbde

File tree

2 files changed

+52
-1
lines changed

2 files changed

+52
-1
lines changed

src/config/io.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ const SHELL_ENV_EXPECTED_KEYS = [
7171
"OPENCLAW_GATEWAY_PASSWORD",
7272
];
7373

74+
const OPEN_DM_POLICY_ALLOW_FROM_RE =
75+
/^(?<policyPath>[a-z0-9_.-]+)\s*=\s*"open"\s+requires\s+(?<allowPath>[a-z0-9_.-]+)(?:\s+\(or\s+[a-z0-9_.-]+\))?\s+to include "\*"$/i;
76+
7477
const CONFIG_AUDIT_LOG_FILENAME = "config-audit.jsonl";
7578
const loggedInvalidConfigs = new Set<string>();
7679

@@ -136,6 +139,27 @@ function hashConfigRaw(raw: string | null): string {
136139
.digest("hex");
137140
}
138141

142+
function formatConfigValidationFailure(pathLabel: string, issueMessage: string): string {
143+
const match = issueMessage.match(OPEN_DM_POLICY_ALLOW_FROM_RE);
144+
const policyPath = match?.groups?.policyPath?.trim();
145+
const allowPath = match?.groups?.allowPath?.trim();
146+
if (!policyPath || !allowPath) {
147+
return `Config validation failed: ${pathLabel}: ${issueMessage}`;
148+
}
149+
150+
return [
151+
`Config validation failed: ${pathLabel}`,
152+
"",
153+
`Configuration mismatch: ${policyPath} is "open", but ${allowPath} does not include "*".`,
154+
"",
155+
"Fix with:",
156+
` openclaw config set ${allowPath} '["*"]'`,
157+
"",
158+
"Or switch policy:",
159+
` openclaw config set ${policyPath} "pairing"`,
160+
].join("\n");
161+
}
162+
139163
function isNumericPathSegment(raw: string): boolean {
140164
return /^[0-9]+$/.test(raw);
141165
}
@@ -999,7 +1023,8 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
9991023
if (!validated.ok) {
10001024
const issue = validated.issues[0];
10011025
const pathLabel = issue?.path ? issue.path : "<root>";
1002-
throw new Error(`Config validation failed: ${pathLabel}: ${issue?.message ?? "invalid"}`);
1026+
const issueMessage = issue?.message ?? "invalid";
1027+
throw new Error(formatConfigValidationFailure(pathLabel, issueMessage));
10031028
}
10041029
if (validated.warnings.length > 0) {
10051030
const details = validated.warnings

src/config/io.write-config.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,32 @@ describe("config io write", () => {
9696
});
9797
});
9898

99+
it('shows actionable guidance for dmPolicy="open" without wildcard allowFrom', async () => {
100+
await withTempHome("openclaw-config-io-", async (home) => {
101+
const io = createConfigIO({
102+
env: {} as NodeJS.ProcessEnv,
103+
homedir: () => home,
104+
logger: silentLogger,
105+
});
106+
107+
const invalidConfig = {
108+
channels: {
109+
telegram: {
110+
dmPolicy: "open",
111+
allowFrom: [],
112+
},
113+
},
114+
};
115+
116+
await expect(io.writeConfigFile(invalidConfig)).rejects.toThrow(
117+
"openclaw config set channels.telegram.allowFrom '[\"*\"]'",
118+
);
119+
await expect(io.writeConfigFile(invalidConfig)).rejects.toThrow(
120+
'openclaw config set channels.telegram.dmPolicy "pairing"',
121+
);
122+
});
123+
});
124+
99125
it("honors explicit unset paths when schema defaults would otherwise reappear", async () => {
100126
await withTempHome("openclaw-config-io-", async (home) => {
101127
const { configPath, io, snapshot } = await writeConfigAndCreateIo({

0 commit comments

Comments
 (0)