Skip to content

Commit fc49e59

Browse files
committed
fix(models): handle watcher errors, close on shutdown, rewarm after invalidate
Addresses three ClawSweeper findings on the fs-watcher commit: - [P1] auth-profile watcher now handles chokidar 'error' events (logs + closes once) mirroring the gateway config-reload pattern. Without this, an unhandled error from chokidar can crash the gateway. - [P2] auth-profile watcher handle is pushed into postReadySidecars so stopPostReadySidecarsAfterCloseStarted closes it on gateway shutdown. - [P2] auth-failure and file-change invalidation paths now schedule a background rewarm (with a 'reason=' log line). Without this, the next /models call after an invalidation paid the slow per-provider path until the next reload. The warmer's existing generation counter handles concurrent rewarms safely.
1 parent 8ad489a commit fc49e59

2 files changed

Lines changed: 44 additions & 5 deletions

File tree

src/agents/auth-profiles-watcher.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,14 @@ export type AuthProfilesWatcherHandle = {
1313
stop: () => Promise<void>;
1414
};
1515

16+
type WatcherLog = {
17+
warn: (msg: string) => void;
18+
};
19+
1620
export function watchAuthProfilesForChanges(params: {
1721
cfg: OpenClawConfig;
1822
onChange: () => void;
23+
log?: WatcherLog;
1924
}): AuthProfilesWatcherHandle {
2025
const watchPaths = listAgentIds(params.cfg).map((agentId) =>
2126
path.join(resolveAgentDir(params.cfg, agentId), "auth-profiles.json"),
@@ -25,16 +30,26 @@ export function watchAuthProfilesForChanges(params: {
2530
awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 },
2631
usePolling: Boolean(process.env.VITEST),
2732
});
33+
let closed = false;
2834
watcher.on("all", () => {
2935
try {
3036
params.onChange();
3137
} catch {
3238
// onChange errors must not crash the watcher.
3339
}
3440
});
41+
watcher.on("error", (err) => {
42+
if (closed) {
43+
return;
44+
}
45+
closed = true;
46+
params.log?.warn(`auth-profile watcher error: ${String(err)}`);
47+
void watcher.close().catch(() => {});
48+
});
3549
return {
3650
stop: async () => {
37-
await watcher.close();
51+
closed = true;
52+
await watcher.close().catch(() => {});
3853
},
3954
};
4055
}

src/gateway/server-startup-post-attach.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,18 +1019,42 @@ export async function startGatewayPostAttachRuntime(
10191019
});
10201020

10211021
void sidecarsPromise
1022-
.then(async () => {
1022+
.then(async (sidecarsResult) => {
10231023
if (params.minimalTestGateway) {
10241024
return;
10251025
}
10261026
const { clearCurrentProviderAuthState, warmCurrentProviderAuthState } =
10271027
await import("../agents/model-provider-auth.js");
10281028
const { setAuthProfileFailureHook } = await import("../agents/auth-profiles.js");
10291029
const { watchAuthProfilesForChanges } = await import("../agents/auth-profiles-watcher.js");
1030-
setAuthProfileFailureHook(() => clearCurrentProviderAuthState());
1031-
watchAuthProfilesForChanges({
1030+
const scheduleAuthMapRewarm = (reason: string) => {
1031+
const startMs = Date.now();
1032+
void warmCurrentProviderAuthState(params.cfgAtStart)
1033+
.then(() => {
1034+
params.log.info(
1035+
`provider auth state re-warmed (${reason}) in ${Date.now() - startMs}ms`,
1036+
);
1037+
})
1038+
.catch((err) => {
1039+
params.log.warn(`provider auth state rewarm failed: ${String(err)}`);
1040+
});
1041+
};
1042+
setAuthProfileFailureHook(() => {
1043+
clearCurrentProviderAuthState();
1044+
scheduleAuthMapRewarm("auth-profile-failure");
1045+
});
1046+
const authProfilesWatcher = watchAuthProfilesForChanges({
10321047
cfg: params.cfgAtStart,
1033-
onChange: () => clearCurrentProviderAuthState(),
1048+
onChange: () => {
1049+
clearCurrentProviderAuthState();
1050+
scheduleAuthMapRewarm("auth-profiles.json change");
1051+
},
1052+
log: params.log,
1053+
});
1054+
sidecarsResult.postReadySidecars.push({
1055+
stop: () => {
1056+
void authProfilesWatcher.stop();
1057+
},
10341058
});
10351059
const startMs = Date.now();
10361060
await warmCurrentProviderAuthState(params.cfgAtStart);

0 commit comments

Comments
 (0)