fix(gateway): log secrets handler error details

This commit is contained in:
Peter Steinberger
2026-05-02 10:31:32 +01:00
parent a0c3cd6878
commit 28c852fee5
3 changed files with 44 additions and 6 deletions

View File

@@ -30,6 +30,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Gateway/secrets: include the caught error message in `secrets.reload` and `secrets.resolve` warning logs while keeping RPC errors generic, so operators can diagnose reload and permission failures. Thanks @davidangularme.
- fix(infra): block workspace state-directory env override [AI]. (#75940) Thanks @pgondhi987.
- MCP/OpenAI: normalize parameter-free tool schemas whose top-level object `properties` is missing, null, or invalid before sending tools to OpenAI, so MCP tools without params stay usable. Fixes #75362. Thanks @tolkonepiu and @SymbolStar.
- TTS: honor explicit short `[[tts:text]]...[[/tts:text]]` blocks while keeping untagged short auto-TTS suppressed, so tagged voice replies are synthesized instead of being dropped as empty voice-only payloads. Fixes #73758. Thanks @yfge.

View File

@@ -50,6 +50,7 @@ describe("secrets handlers", () => {
diagnostics: string[];
inactiveRefPaths: string[];
}>;
log?: { warn?: (message: string) => void };
}) {
const reloadSecrets = overrides?.reloadSecrets ?? (async () => ({ warningCount: 0 }));
const resolveSecrets =
@@ -62,6 +63,7 @@ describe("secrets handlers", () => {
return createSecretsHandlers({
reloadSecrets,
resolveSecrets,
log: overrides?.log,
});
}
@@ -75,8 +77,10 @@ describe("secrets handlers", () => {
});
it("returns unavailable when reload fails", async () => {
const warn = vi.fn();
const handlers = createHandlers({
reloadSecrets: vi.fn().mockRejectedValue(new Error("reload failed")),
reloadSecrets: vi.fn().mockRejectedValue(new Error("disk full")),
log: { warn },
});
const respond = vi.fn();
await invokeSecretsReload({ handlers, respond });
@@ -88,6 +92,7 @@ describe("secrets handlers", () => {
message: "secrets.reload failed",
}),
);
expect(warn).toHaveBeenCalledWith(expect.stringContaining("disk full"));
});
it("resolves requested command secret assignments from the active snapshot", async () => {
@@ -189,12 +194,13 @@ describe("secrets handlers", () => {
});
it("returns unavailable when secrets.resolve handler returns an invalid payload shape", async () => {
const warn = vi.fn();
const resolveSecrets = vi.fn().mockResolvedValue({
assignments: [{ path: TALK_TEST_PROVIDER_API_KEY_PATH, pathSegments: [""], value: "sk" }],
diagnostics: [],
inactiveRefPaths: [],
});
const handlers = createHandlers({ resolveSecrets });
const handlers = createHandlers({ resolveSecrets, log: { warn } });
const respond = vi.fn();
await invokeSecretsResolve({
handlers,
@@ -210,5 +216,32 @@ describe("secrets handlers", () => {
message: "secrets.resolve failed",
}),
);
expect(warn).toHaveBeenCalledWith(
expect.stringContaining("secrets.resolve returned invalid payload."),
);
});
it("logs error details when secrets.resolve throws", async () => {
const warn = vi.fn();
const handlers = createHandlers({
resolveSecrets: vi.fn().mockRejectedValue(new Error("EACCES: permission denied")),
log: { warn },
});
const respond = vi.fn();
await invokeSecretsResolve({
handlers,
respond,
commandName: "memory status",
targetIds: ["talk.providers.*.apiKey"],
});
expect(respond).toHaveBeenCalledWith(
false,
undefined,
expect.objectContaining({
code: "UNAVAILABLE",
message: "secrets.resolve failed",
}),
);
expect(warn).toHaveBeenCalledWith(expect.stringContaining("EACCES: permission denied"));
});
});

View File

@@ -8,6 +8,10 @@ import {
} from "../protocol/index.js";
import type { GatewayRequestHandlers } from "./types.js";
function errorMessage(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}
function invalidSecretsResolveField(
errors: ErrorObject[] | null | undefined,
): "commandName" | "targetIds" {
@@ -43,8 +47,8 @@ export function createSecretsHandlers(params: {
try {
const result = await params.reloadSecrets();
respond(true, { ok: true, warningCount: result.warningCount });
} catch {
params.log?.warn?.("secrets.reload failed");
} catch (error) {
params.log?.warn?.(`secrets.reload failed: ${errorMessage(error)}`);
respond(false, undefined, errorShape(ErrorCodes.UNAVAILABLE, "secrets.reload failed"));
}
},
@@ -100,8 +104,8 @@ export function createSecretsHandlers(params: {
throw new Error("secrets.resolve returned invalid payload.");
}
respond(true, payload);
} catch {
params.log?.warn?.("secrets.resolve failed");
} catch (error) {
params.log?.warn?.(`secrets.resolve failed: ${errorMessage(error)}`);
respond(false, undefined, errorShape(ErrorCodes.UNAVAILABLE, "secrets.resolve failed"));
}
},