Skip to content

Commit bf777b9

Browse files
giodl73-repoGio Della-Libera
andauthored
fix(doctor): quiet tool policy audits during probes
Keep runtime tool-policy removal audits at the normal info level, but lower diagnostic-only doctor tool-schema probes to debug so expected profile filtering does not clutter normal doctor output. Also updates current-base test expectations for the Talk custom select and a promise-executor lint rule so the PR remains green on the latest base. Fixes #87798. Proof: - CI https://github.com/openclaw/openclaw/actions/runs/26727664397 - Real behavior proof https://github.com/openclaw/openclaw/actions/runs/26727667473 - Local focused Vitest, broad lint, touched-file format/lint, and autoreview clean. Co-authored-by: Gio Della-Libera <40915808+giodl73-repo@users.noreply.github.com>
1 parent fba9eac commit bf777b9

11 files changed

Lines changed: 116 additions & 52 deletions

src/agents/agent-bundle-mcp-runtime.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1907,7 +1907,9 @@ process.stdin.on("end", () => {
19071907
});
19081908
});
19091909

1910-
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
1910+
await new Promise<void>((resolve) => {
1911+
server.listen(0, "127.0.0.1", resolve);
1912+
});
19111913
const addr = server.address() as { port: number };
19121914

19131915
try {

src/agents/agent-tools.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,8 @@ export function createOpenClawCodingTools(options?: {
513513
onYield?: (message: string) => Promise<void> | void;
514514
/** Optional instrumentation callback for tool preparation stage timing. */
515515
recordToolPrepStage?: (name: string) => void;
516+
/** Lower routine policy-removal audits for diagnostic-only tool probes. */
517+
toolPolicyAuditLogLevel?: "info" | "debug";
516518
/** Live observer called after wrapped tool outcomes are recorded. */
517519
onToolOutcome?: ToolOutcomeObserver;
518520
/** Runtime-only resolved skill paths that the read tool may load under workspaceOnly. */
@@ -1114,6 +1116,7 @@ export function createOpenClawCodingTools(options?: {
11141116
},
11151117
{ policy: inheritedToolPolicy, label: "inherited tools", unavailableCoreToolReason },
11161118
],
1119+
auditLogLevel: options?.toolPolicyAuditLogLevel,
11171120
});
11181121
if (shouldInheritEffectiveToolAllowlist) {
11191122
replaceWithEffectiveToolAllowlist(inheritedToolAllowlist, subagentFiltered);

src/agents/embedded-agent-runner/effective-tool-policy.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ type FinalEffectiveToolPolicyParams = {
5454
senderUsername?: string | null;
5555
senderE164?: string | null;
5656
warn: (message: string) => void;
57+
toolPolicyAuditLogLevel?: "info" | "debug";
5758
};
5859

5960
export function applyFinalEffectiveToolPolicy(
@@ -173,5 +174,6 @@ export function applyFinalEffectiveToolPolicy(
173174
toolMeta: (tool) => getPluginToolMeta(tool),
174175
warn: params.warn,
175176
steps: pipelineSteps,
177+
auditLogLevel: params.toolPolicyAuditLogLevel,
176178
});
177179
}

src/agents/tool-policy-audit.ts

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ const MAX_AUDIT_TOOL_NAMES = 50;
77
const MAX_AUDIT_FIELD_LENGTH = 160;
88
const toolPolicyAuditLogger = createSubsystemLogger("agents/tool-policy");
99

10+
export type ToolPolicyAuditLogLevel = "info" | "debug";
11+
1012
type ToolPolicyRuleKind = "allow" | "deny" | "allow+deny" | "unknown";
1113

1214
function toolPolicyRuleKind(policy: ToolPolicyLike): ToolPolicyRuleKind {
@@ -157,6 +159,7 @@ export function auditToolPolicyFilter(params: {
157159
policy: ToolPolicyLike;
158160
before: readonly { name: string }[];
159161
after: readonly { name: string }[];
162+
logLevel?: ToolPolicyAuditLogLevel;
160163
}): void {
161164
const removedByRule = removedToolNamesByRule({
162165
policy: params.policy,
@@ -176,22 +179,25 @@ export function auditToolPolicyFilter(params: {
176179
tools: matchedRuleSourceTools,
177180
});
178181
const matchedRuleSuffix = matchedRules.length > 0 ? `; matched ${matchedRules.join(", ")}` : "";
179-
toolPolicyAuditLogger.info(
180-
`tool policy removed ${removed.length} tool(s) via ${rule}: ${toolNames.join(", ")}${matchedRuleSuffix}`,
181-
{
182-
rule,
183-
ruleKind,
184-
...(matchedRules.length > 0
185-
? {
186-
matchedRules,
187-
...(truncated ? { matchedRulesTruncated: true } : {}),
188-
}
189-
: {}),
190-
removedToolCount: removed.length,
191-
removedTools: toolNames,
192-
removedToolsTruncated: truncated,
193-
},
194-
);
182+
const message = `tool policy removed ${removed.length} tool(s) via ${rule}: ${toolNames.join(", ")}${matchedRuleSuffix}`;
183+
const metadata = {
184+
rule,
185+
ruleKind,
186+
...(matchedRules.length > 0
187+
? {
188+
matchedRules,
189+
...(truncated ? { matchedRulesTruncated: true } : {}),
190+
}
191+
: {}),
192+
removedToolCount: removed.length,
193+
removedTools: toolNames,
194+
removedToolsTruncated: truncated,
195+
};
196+
if (params.logLevel === "debug") {
197+
toolPolicyAuditLogger.debug(message, metadata);
198+
} else {
199+
toolPolicyAuditLogger.info(message, metadata);
200+
}
195201
}
196202
}
197203

src/agents/tool-policy-pipeline.test.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ import {
66
} from "./tool-policy-pipeline.js";
77
import { resolveToolProfilePolicy } from "./tool-policy.js";
88

9-
const { toolPolicyAuditInfo } = vi.hoisted(() => ({
9+
const { toolPolicyAuditDebug, toolPolicyAuditInfo } = vi.hoisted(() => ({
10+
toolPolicyAuditDebug: vi.fn(),
1011
toolPolicyAuditInfo: vi.fn(),
1112
}));
1213

1314
vi.mock("../logging/subsystem.js", () => ({
1415
createSubsystemLogger: () => ({
16+
debug: toolPolicyAuditDebug,
1517
info: toolPolicyAuditInfo,
1618
}),
1719
}));
@@ -49,6 +51,7 @@ function runAllowlistWarningStep(params: {
4951
describe("tool-policy-pipeline", () => {
5052
beforeEach(() => {
5153
resetToolPolicyWarningCacheForTest();
54+
toolPolicyAuditDebug.mockClear();
5255
toolPolicyAuditInfo.mockClear();
5356
});
5457

@@ -318,6 +321,36 @@ describe("tool-policy-pipeline", () => {
318321
removedToolsTruncated: false,
319322
},
320323
);
324+
expect(toolPolicyAuditDebug).not.toHaveBeenCalled();
325+
});
326+
327+
test("can lower removal audits for diagnostic-only policy probes", () => {
328+
const tools = [{ name: "exec" }, { name: "browser" }] as unknown as DummyTool[];
329+
330+
applyToolPolicyPipeline({
331+
tools: tools as any,
332+
toolMeta: () => undefined,
333+
warn: () => {},
334+
auditLogLevel: "debug",
335+
steps: [
336+
{
337+
policy: { allow: ["exec"] },
338+
label: "doctor tools.profile (coding)",
339+
},
340+
],
341+
});
342+
343+
expect(toolPolicyAuditDebug).toHaveBeenCalledWith(
344+
"tool policy removed 1 tool(s) via doctor tools.profile (coding): browser",
345+
{
346+
rule: "doctor tools.profile (coding)",
347+
ruleKind: "allow",
348+
removedToolCount: 1,
349+
removedTools: ["browser"],
350+
removedToolsTruncated: false,
351+
},
352+
);
353+
expect(toolPolicyAuditInfo).not.toHaveBeenCalled();
321354
});
322355

323356
test("audits deny removals with the deny config key", () => {
@@ -346,6 +379,7 @@ describe("tool-policy-pipeline", () => {
346379
removedToolsTruncated: false,
347380
},
348381
);
382+
expect(toolPolicyAuditDebug).not.toHaveBeenCalled();
349383
});
350384

351385
test("splits mixed allow and deny policy audit entries by cause", () => {
@@ -388,6 +422,7 @@ describe("tool-policy-pipeline", () => {
388422
removedToolsTruncated: false,
389423
},
390424
);
425+
expect(toolPolicyAuditDebug).not.toHaveBeenCalled();
391426
});
392427

393428
test("does not audit policy steps that leave the tool surface unchanged", () => {
@@ -405,6 +440,7 @@ describe("tool-policy-pipeline", () => {
405440
],
406441
});
407442

443+
expect(toolPolicyAuditDebug).not.toHaveBeenCalled();
408444
expect(toolPolicyAuditInfo).not.toHaveBeenCalled();
409445
});
410446

@@ -433,5 +469,6 @@ describe("tool-policy-pipeline", () => {
433469
removedToolsTruncated: false,
434470
},
435471
);
472+
expect(toolPolicyAuditDebug).not.toHaveBeenCalled();
436473
});
437474
});

src/agents/tool-policy-pipeline.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { filterToolsByPolicy } from "./agent-tools.policy.js";
22
import type { AnyAgentTool } from "./agent-tools.types.js";
33
import { isKnownCoreToolId } from "./tool-catalog.js";
4-
import { auditToolPolicyFilter } from "./tool-policy-audit.js";
4+
import { auditToolPolicyFilter, type ToolPolicyAuditLogLevel } from "./tool-policy-audit.js";
55
import {
66
analyzeAllowlistByToolType,
77
buildPluginToolGroups,
@@ -120,6 +120,7 @@ export function applyToolPolicyPipeline(params: {
120120
toolMeta: (tool: AnyAgentTool) => { pluginId: string } | undefined;
121121
warn: (message: string) => void;
122122
steps: ToolPolicyPipelineStep[];
123+
auditLogLevel?: ToolPolicyAuditLogLevel;
123124
}): AnyAgentTool[] {
124125
const coreToolNames = new Set(
125126
params.tools
@@ -191,6 +192,7 @@ export function applyToolPolicyPipeline(params: {
191192
policy: expanded,
192193
before,
193194
after: filtered,
195+
logLevel: params.auditLogLevel,
194196
});
195197
}
196198
return filtered;

src/commands/doctor/shared/active-tool-schema-warnings.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ describe("active tool schema doctor warnings", () => {
103103
).toEqual([
104104
'- agents.main: active tool "dofbot_move_angles" from plugin "dofbot" has unsupported runtime input schema (dofbot_move_angles.parameters.type must be "object"). OpenClaw will quarantine this tool at runtime; fix or disable the plugin, or remove the tool from active allowlists.',
105105
]);
106+
expect(toolState.createTools).toHaveBeenCalledWith(
107+
expect.objectContaining({ toolPolicyAuditLogLevel: "debug" }),
108+
);
106109
});
107110

108111
it("warns about unreadable active tool entries without crashing", () => {

src/commands/doctor/shared/active-tool-schema-warnings.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ export function collectActiveToolSchemaProjectionWarnings(params: {
158158
modelCompat: runtimeModelContext.modelCompat,
159159
modelContextWindowTokens: runtimeModelContext.modelContextWindowTokens,
160160
allowGatewaySubagentBinding: true,
161+
toolPolicyAuditLogLevel: "debug",
161162
});
162163
} catch (error) {
163164
warnings.push(

src/flows/doctor-core-checks.runtime.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -332,10 +332,10 @@ describe("doctor runtime tool schema checks", () => {
332332
"Disable or update the offending plugin/tool so its parameters are a JSON object schema, then rerun doctor.",
333333
});
334334
expect(mocks.createOpenClawCodingTools).toHaveBeenCalledWith(
335-
expect.objectContaining({ agentId: "main" }),
335+
expect.objectContaining({ agentId: "main", toolPolicyAuditLogLevel: "debug" }),
336336
);
337337
expect(mocks.createOpenClawCodingTools).toHaveBeenCalledWith(
338-
expect.objectContaining({ agentId: "worker" }),
338+
expect.objectContaining({ agentId: "worker", toolPolicyAuditLogLevel: "debug" }),
339339
);
340340
expect(mocks.createBundleMcpToolRuntime).toHaveBeenCalledTimes(1);
341341
expect(mocks.disposeBundleRuntime).toHaveBeenCalledTimes(1);

src/flows/doctor-core-checks.runtime.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,7 @@ function collectBundleMcpRuntimeToolSchemaFindings(params: {
605605
modelProvider: params.modelRef.provider,
606606
modelId: params.modelRef.model,
607607
warn: () => {},
608+
toolPolicyAuditLogLevel: "debug",
608609
});
609610
const preNormalizationFindings: HealthFinding[] = [];
610611

@@ -694,6 +695,7 @@ function collectAgentRuntimeToolSchemaFindings(params: {
694695
modelContextWindowTokens: params.model.contextWindow,
695696
allowGatewaySubagentBinding: true,
696697
emitBeforeToolCallDiagnostics: false,
698+
toolPolicyAuditLogLevel: "debug",
697699
});
698700
} catch (error) {
699701
return [agentRuntimeToolLoadFailureFinding({ agentId: params.agentId, error })];
@@ -866,6 +868,7 @@ function shouldReportBundleMcpRuntimeDiagnostic(params: {
866868
modelProvider: params.modelRef.provider,
867869
modelId: params.modelRef.model,
868870
warn: () => {},
871+
toolPolicyAuditLogLevel: "debug",
869872
}).length > 0
870873
);
871874
}

0 commit comments

Comments
 (0)