diff --git a/src/config/config.test.ts b/src/config/config.test.ts index f271fd211..9d49b5416 100644 --- a/src/config/config.test.ts +++ b/src/config/config.test.ts @@ -1685,3 +1685,46 @@ describe("multi-agent agentDir validation", () => { }); }); }); + +describe("config preservation on validation failure", () => { + it("preserves unknown fields via passthrough", async () => { + vi.resetModules(); + const { validateConfigObject } = await import("./config.js"); + const res = validateConfigObject({ + agents: { list: [{ id: "pi" }] }, + customUnknownField: { nested: "value" }, + }); + expect(res.ok).toBe(true); + expect( + (res as { config: Record }).config.customUnknownField, + ).toEqual({ + nested: "value", + }); + }); + + it("preserves config data when validation fails", async () => { + await withTempHome(async (home) => { + const configDir = path.join(home, ".clawdbot"); + await fs.mkdir(configDir, { recursive: true }); + await fs.writeFile( + path.join(configDir, "clawdbot.json"), + JSON.stringify({ + agents: { list: [{ id: "pi" }] }, + routing: { allowFrom: ["+15555550123"] }, + customData: { preserved: true }, + }), + "utf-8", + ); + + vi.resetModules(); + const { readConfigFileSnapshot } = await import("./config.js"); + const snap = await readConfigFileSnapshot(); + + expect(snap.valid).toBe(false); + expect(snap.legacyIssues.length).toBeGreaterThan(0); + expect((snap.config as Record).customData).toEqual({ + preserved: true, + }); + }); + }); +}); diff --git a/src/config/io.ts b/src/config/io.ts index af86cdc7e..b6388acc6 100644 --- a/src/config/io.ts +++ b/src/config/io.ts @@ -296,7 +296,7 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) { raw, parsed: parsedRes.parsed, valid: false, - config: {}, + config: resolved as ClawdbotConfig, issues: validated.issues, legacyIssues, }; diff --git a/src/config/zod-schema.ts b/src/config/zod-schema.ts index b6a912e54..d72b855f4 100644 --- a/src/config/zod-schema.ts +++ b/src/config/zod-schema.ts @@ -1757,6 +1757,7 @@ export const ClawdbotSchema = z }) .optional(), }) + .passthrough() .superRefine((cfg, ctx) => { const agents = cfg.agents?.list ?? []; if (agents.length === 0) return;