diff --git a/CHANGELOG.md b/CHANGELOG.md index 00e5f147e..a32e7de4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ Docs: https://docs.clawd.bot - Model catalog: avoid caching import failures, log transient discovery errors, and keep partial results. (#1332) — thanks @dougvk. - Doctor: clarify plugin auto-enable hint text in the startup banner. - Gateway: clarify unauthorized handshake responses with token/password mismatch guidance. +- Gateway: clarify connect/validation errors for gateway params. (#1347) — thanks @vignesh07. - Gateway: preserve restart wake routing + thread replies across restarts. (#1337) — thanks @John-Rood. - Gateway: reschedule per-agent heartbeats on config hot reload without restarting the runner. - Config: log invalid config issues once per run and keep invalid-config errors stackless. diff --git a/src/gateway/protocol/index.test.ts b/src/gateway/protocol/index.test.ts new file mode 100644 index 000000000..828951366 --- /dev/null +++ b/src/gateway/protocol/index.test.ts @@ -0,0 +1,65 @@ +import { describe, expect, it } from "vitest"; +import type { ErrorObject } from "ajv"; + +import { formatValidationErrors } from "./index.js"; + +const makeError = (overrides: Partial): ErrorObject => ({ + keyword: "type", + instancePath: "", + schemaPath: "#/", + params: {}, + message: "validation error", + ...overrides, +}); + +describe("formatValidationErrors", () => { + it("returns unknown validation error when missing errors", () => { + expect(formatValidationErrors(undefined)).toBe("unknown validation error"); + expect(formatValidationErrors(null)).toBe("unknown validation error"); + }); + + it("returns unknown validation error when errors list is empty", () => { + expect(formatValidationErrors([])).toBe("unknown validation error"); + }); + + it("formats additionalProperties at root", () => { + const err = makeError({ + keyword: "additionalProperties", + params: { additionalProperty: "token" }, + }); + + expect(formatValidationErrors([err])).toBe("at root: unexpected property 'token'"); + }); + + it("formats additionalProperties with instancePath", () => { + const err = makeError({ + keyword: "additionalProperties", + instancePath: "/auth", + params: { additionalProperty: "token" }, + }); + + expect(formatValidationErrors([err])).toBe("at /auth: unexpected property 'token'"); + }); + + it("formats message with path for other errors", () => { + const err = makeError({ + keyword: "required", + instancePath: "/auth", + message: "must have required property 'token'", + }); + + expect(formatValidationErrors([err])).toBe("at /auth: must have required property 'token'"); + }); + + it("de-dupes repeated entries", () => { + const err = makeError({ + keyword: "required", + instancePath: "/auth", + message: "must have required property 'token'", + }); + + expect(formatValidationErrors([err, err])).toBe( + "at /auth: must have required property 'token'", + ); + }); +}); diff --git a/src/gateway/protocol/index.ts b/src/gateway/protocol/index.ts index 7d82e0ecc..dba040fe5 100644 --- a/src/gateway/protocol/index.ts +++ b/src/gateway/protocol/index.ts @@ -310,7 +310,7 @@ export const validateWebLoginStartParams = export const validateWebLoginWaitParams = ajv.compile(WebLoginWaitParamsSchema); export function formatValidationErrors(errors: ErrorObject[] | null | undefined) { - if (!errors) return "unknown validation error"; + if (!errors?.length) return "unknown validation error"; const parts: string[] = []; @@ -328,13 +328,18 @@ export function formatValidationErrors(errors: ErrorObject[] | null | undefined) } } - const message = typeof err?.message === "string" ? err.message : "validation error"; + const message = + typeof err?.message === "string" && err.message.trim() ? err.message : "validation error"; const where = instancePath ? `at ${instancePath}: ` : ""; parts.push(`${where}${message}`); } // De-dupe while preserving order. - const unique = Array.from(new Set(parts)); + const unique = Array.from(new Set(parts.filter((part) => part.trim()))); + if (!unique.length) { + const fallback = ajv.errorsText(errors, { separator: "; " }); + return fallback || "unknown validation error"; + } return unique.join("; "); }