toeverything/AFFiNE · error · InvalidAppConfigInput
invalid_app_config_input
invalid_app_config_input
Error message
Invalid app config input: The active signing key changed. Reload and try again.
What it means
Signing-key rotation runs inside appConfig.mutate and asserts the currently active key still has the id the actor read before starting (active.id !== expectedActiveKeyId). If another rotation landed in between, the mutate callback aborts with InvalidAppConfigInput telling the admin to reload. This is an optimistic-concurrency guard so two rotations cannot interleave.
Solutions
- Reload the signing-key snapshot (snapshotMetadata) and re-run rotate with the new active key id
- Make the rotate button single-flight/disabled while a rotation is in flight
- Serialize rotations: only one admin performs key rotation at a time (coordination or lock)
Example fix
// before
await signingKey.rotate(actorId, expectedActiveKeyId);
// after
for (let attempt = 0; attempt < 3; attempt++) {
try {
await signingKey.rotate(actorId, expectedActiveKeyId);
break;
} catch (e) {
if (e.code !== 'invalid_app_config_input') throw e;
const snapshot = await signingKey.snapshotMetadata();
expectedActiveKeyId = snapshot.activeKeyId; // someone else rotated first
}
} Defensive patterns
Strategy: retry
Validate before calling
const snapshot = await signingKey.snapshotMetadata();
const expectedActiveKeyId = snapshot.keys.find(k => k.status === 'active')?.id;
if (!expectedActiveKeyId) {
throw new Error('No active signing key — resolve key store state first');
}
await signingKey.rotate(actorId, expectedActiveKeyId); Type guard
function isInvalidAppConfigInput(e: unknown): boolean {
return (
typeof e === 'object' &&
e !== null &&
'code' in e &&
(e as { code?: string }).code === 'invalid_app_config_input'
);
} Try / catch
Catch invalid_app_config_input from rotate, re-read snapshotMetadata for the new active key id, and retry the rotation with that id — bounded to a few attempts before surfacing a concurrent-rotation conflict.
Prevention
- Reload the key snapshot right before initiating rotation
- Single-flight/disable the rotate button while a rotation is in flight
- Coordinate so only one operator rotates keys at a time
When it happens
Trigger: Two admins (or two tabs/scripts) call rotate with the same expectedActiveKeyId; the first succeeds and demotes the key to retiring, the second's precondition now fails. Also a double-click that fires rotate twice with the same snapshot.
Common situations: Admin UI keeps a stale snapshot while another operator rotates first; the rotate button lacks single-flight and double-fires; an automation script races a manual rotation.
Related errors
AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18).
Data as JSON: /api/errors/e4f866228404b43d.
Report an issue: GitHub.
Appendix: source
Thrown at packages/backend/server/src/core/auth/signing-key.ts:145
const replacement = this.generate('admin');
const now = new Date();
const verifyUntil = new Date(
now.getTime() +
(this.config.auth.token.accessTokenTtl + CLOCK_SKEW_SECONDS) * 1000
);
const updated = await this.models.appConfig.mutate(
SIGNING_KEY_STORE_ID,
actorId,
value => {
const current = this.parse(value);
const active = current.find(key => key.status === 'active');
if (!active) {
throw new Error(
'Auth session requires exactly one active signing key.'
);
}
if (active.id !== expectedActiveKeyId) {
throw new InvalidAppConfigInput({
message: 'The active signing key changed. Reload and try again.',
});
}
return [
...current.map(key =>
key.status === 'active'
? {
...key,
status: 'retiring' as const,
retiredAt: now.toISOString(),
verifyUntil: verifyUntil.toISOString(),
}
: key
),
replacement,
];
}
);View on GitHub (pinned to b4c8548c09)