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: {