toeverything/AFFiNE · error · Error
App config paths must not overlap: ${overlappingKey} and ${k
Error message
App config paths must not overlap: ${overlappingKey} and ${key} What it means
Plain Error thrown by AppConfigModel (packages/backend/server/src/models/config.ts:34) during a bulk app-config update. For each key in updates it scans existing config ids plus previously-seen update keys for a hierarchical overlap: one is a prefix of the other via startsWith('x.'). The guard prevents two configs from shadowing each other in a dotted namespace (e.g. 'a' vs 'a.b').
Source
Thrown at packages/backend/server/src/models/config.ts:34
@Transactional()
async save(user: string, updates: Array<{ key: string; value: any }>) {
await this.db
.$executeRaw`SELECT pg_advisory_xact_lock(hashtextextended(${'app-config-paths'}, 0))`;
const existing = await this.db.appConfig.findMany({
select: { id: true },
});
const updateKeys = updates.map(update => update.key);
for (const [index, key] of updateKeys.entries()) {
const overlappingKey = [
...existing.map(config => config.id),
...updateKeys.slice(0, index),
].find(
candidate =>
candidate !== key &&
(candidate.startsWith(`${key}.`) || key.startsWith(`${candidate}.`))
);
if (overlappingKey) {
throw new Error(
`App config paths must not overlap: ${overlappingKey} and ${key}`
);
}
}
return await Promise.allSettled(
updates.map(async update => {
return this.db.appConfig.upsert({
where: { id: update.key },
update: { value: update.value, lastUpdatedBy: user },
create: { id: update.key, value: update.value, lastUpdatedBy: user },
});
})
);
}
async get(key: string) {
return await this.db.appConfig.findUnique({ where: { id: key } });View on GitHub (pinned to 26c515e050)
Solutions
- Split the batch so ancestor and descendant are never updated together; update leaves first, then parents.
- Delete the colliding existing key (db.appConfig.delete) before inserting its ancestor/descendant.
- Flatten your config schema so keys do not share prefixes, or adopt a non-dotted separator.
- Inspect existing ids first: const existing = await db.appConfig.findMany({ select: { id: true } }); and diff against your proposed keys.
Example fix
// before
await configModel.setMany(user, [
{ key: 'feature', value: 'on' },
{ key: 'feature.flag', value: 'off' }, // overlap -> throws
]);
// after
await configModel.setMany(user, [{ key: 'feature.flag', value: 'off' }]);
await db.appConfig.deleteMany({ where: { id: { startsWith: 'feature.' } } });
await configModel.setMany(user, [{ key: 'feature', value: 'on' }]); Defensive patterns
Strategy: validation
Validate before calling
function findOverlap(keys: string[], existing: string[]): [string, string] | null {
for (const key of keys) {
const hit = [...existing, ...keys.filter(k => k !== key)].find(c =>
c !== key && (c.startsWith(`${key}.`) || key.startsWith(`${c}.`))
);
if (hit) return [hit, key];
}
return null;
}
const existingIds = (await db.appConfig.findMany({ select: { id: true } })).map(r => r.id);
if (findOverlap(updates.map(u => u.key), existingIds)) throw new Error('overlap'); Prevention
- Never update a parent key and its dotted descendants in the same batch.
- Delete child keys before introducing an ancestor of them.
- Avoid dotted hierarchical config schemas unless you need prefix semantics.
When it happens
Trigger: Posting an app-config update batch where one key is an ancestor/descendant of another. Triggers when candidate.startsWith(`${key}.`) OR key.startsWith(`${candidate}.`) for any earlier update key or any existing appConfig.id. Example: updating [{key:'feature.x'}, {key:'feature'}] in one call, or adding 'feature' when 'feature.x' already exists.
Common situations: Migrating a flat key to a namespace (or vice versa) without first deleting children; bulk-importing configs with mixed nesting depths; UI saving both a parent toggle and a child setting in the same payload; renaming a config namespace.
Related errors
- Invalid config for module [${module}] with key [${key}] Valu
- invalid_app_config_input
- A config file path is required
- invalid_app_config_input
- Invalid value "${value}" for environment variable ${env}, ex
AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12).
Data as JSON: /api/errors/cf00bb4d69a0b39b.
Report an issue: GitHub.