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
This commit is contained in:
Josh Boys
2026-07-30 08:43:07 +08:00
committed by GitHub
parent 1165be39ff
commit edbb1fca86
2 changed files with 19 additions and 1 deletions
@@ -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: [] });
});
});
@@ -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: {