From edbb1fca86c441bc21fd3eeb85fb4d385b493a75 Mon Sep 17 00:00:00 2001 From: Josh Boys Date: Thu, 30 Jul 2026 08:43:07 +0800 Subject: [PATCH] fix(doctor): fail closed on unknown channel schema (#116025) doctor --fix copies allowFrom into groupAllowFrom for any channel whose capabilities allow the fallback, guarded by a check against the compile-time generated channel schema metadata. That metadata only covers core-compiled channels; extension-installed channels like agentmail have no entry there. The guard treated a missing schema as "no restriction" and wrote groupAllowFrom anyway, which agentmail's own runtime schema then rejected, aborting the whole doctor --fix batch (including unrelated pending migrations). Fail closed instead: without schema info, don't write the field. Fixes #116024 --- .../shared/allowfrom-fallback-migration.test.ts | 16 ++++++++++++++++ .../shared/allowfrom-fallback-migration.ts | 4 +++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/commands/doctor/shared/allowfrom-fallback-migration.test.ts b/src/commands/doctor/shared/allowfrom-fallback-migration.test.ts index a6d2b0a908d9..446137183973 100644 --- a/src/commands/doctor/shared/allowfrom-fallback-migration.test.ts +++ b/src/commands/doctor/shared/allowfrom-fallback-migration.test.ts @@ -130,4 +130,20 @@ describe("doctor group allowFrom fallback migration", () => { expect(maybeRepairGroupAllowFromFallback(cfg)).toEqual({ config: cfg, changes: [] }); }); + + it("skips bundled-extension channels absent from generated metadata", () => { + // agentmail ships as a ClawHub extension and has no generated-metadata schema entry, + // so doctor cannot prove groupAllowFrom is a valid field for it; regression test for a + // bug where the missing entry was treated as "no restriction" and doctor wrote a field + // agentmail's own runtime schema rejects, failing config validation on every run. + const cfg = { + channels: { + agentmail: { + allowFrom: ["someone@example.com"], + }, + }, + }; + + expect(maybeRepairGroupAllowFromFallback(cfg)).toEqual({ config: cfg, changes: [] }); + }); }); diff --git a/src/commands/doctor/shared/allowfrom-fallback-migration.ts b/src/commands/doctor/shared/allowfrom-fallback-migration.ts index 12067284ca3f..2ad2b90a586f 100644 --- a/src/commands/doctor/shared/allowfrom-fallback-migration.ts +++ b/src/commands/doctor/shared/allowfrom-fallback-migration.ts @@ -107,7 +107,9 @@ function schemaAllowsConfigPath(schema: unknown, path: SchemaPath): boolean { function generatedSchemaAllowsGroupAllowFrom(channelName: string, path: SchemaPath): boolean { const schema = findGeneratedChannelConfigSchema(channelName); - return !schema || schemaAllowsConfigPath(schema, path); + // Extension-installed channels (e.g. ClawHub agentmail) have no generated-metadata entry; + // without schema info we can't prove the write is safe, so fail closed rather than open. + return schema !== undefined && schemaAllowsConfigPath(schema, path); } function migrateRecord(params: {