Gateway: serialize secrets activation across reload paths

This commit is contained in:
joshavant
2026-02-22 14:41:26 -08:00
committed by Peter Steinberger
parent fe56700026
commit 2e53033f22

View File

@@ -270,49 +270,59 @@ export async function startGatewayServer(
contextKey: code,
});
};
let secretsActivationTail: Promise<void> = Promise.resolve();
const runWithSecretsActivationLock = async <T>(operation: () => Promise<T>): Promise<T> => {
const run = secretsActivationTail.then(operation, operation);
secretsActivationTail = run.then(
() => undefined,
() => undefined,
);
return await run;
};
const activateRuntimeSecrets = async (
config: OpenClawConfig,
params: { reason: "startup" | "reload" | "restart-check"; activate: boolean },
) => {
try {
const prepared = await prepareSecretsRuntimeSnapshot({ config });
if (params.activate) {
activateSecretsRuntimeSnapshot(prepared);
}
for (const warning of prepared.warnings) {
logSecrets.warn(`[${warning.code}] ${warning.message}`);
}
if (secretsDegraded) {
const recoveredMessage =
"Secret resolution recovered; runtime remained on last-known-good during the outage.";
logSecrets.info(`[SECRETS_RELOADER_RECOVERED] ${recoveredMessage}`);
emitSecretsStateEvent("SECRETS_RELOADER_RECOVERED", recoveredMessage, prepared.config);
}
secretsDegraded = false;
return prepared;
} catch (err) {
const details = String(err);
if (!secretsDegraded) {
logSecrets.error(`[SECRETS_RELOADER_DEGRADED] ${details}`);
if (params.reason !== "startup") {
emitSecretsStateEvent(
"SECRETS_RELOADER_DEGRADED",
`Secret resolution failed; runtime remains on last-known-good snapshot. ${details}`,
config,
);
) =>
await runWithSecretsActivationLock(async () => {
try {
const prepared = await prepareSecretsRuntimeSnapshot({ config });
if (params.activate) {
activateSecretsRuntimeSnapshot(prepared);
}
} else {
logSecrets.warn(`[SECRETS_RELOADER_DEGRADED] ${details}`);
for (const warning of prepared.warnings) {
logSecrets.warn(`[${warning.code}] ${warning.message}`);
}
if (secretsDegraded) {
const recoveredMessage =
"Secret resolution recovered; runtime remained on last-known-good during the outage.";
logSecrets.info(`[SECRETS_RELOADER_RECOVERED] ${recoveredMessage}`);
emitSecretsStateEvent("SECRETS_RELOADER_RECOVERED", recoveredMessage, prepared.config);
}
secretsDegraded = false;
return prepared;
} catch (err) {
const details = String(err);
if (!secretsDegraded) {
logSecrets.error(`[SECRETS_RELOADER_DEGRADED] ${details}`);
if (params.reason !== "startup") {
emitSecretsStateEvent(
"SECRETS_RELOADER_DEGRADED",
`Secret resolution failed; runtime remains on last-known-good snapshot. ${details}`,
config,
);
}
} else {
logSecrets.warn(`[SECRETS_RELOADER_DEGRADED] ${details}`);
}
secretsDegraded = true;
if (params.reason === "startup") {
throw new Error(`Startup failed: required secrets are unavailable. ${details}`, {
cause: err,
});
}
throw err;
}
secretsDegraded = true;
if (params.reason === "startup") {
throw new Error(`Startup failed: required secrets are unavailable. ${details}`, {
cause: err,
});
}
throw err;
}
};
});
// Fail fast before startup if required refs are unresolved.
let cfgAtStart: OpenClawConfig;