diff --git a/apps/controller/src/runtime/openclaw-runtime-model-writer.ts b/apps/controller/src/runtime/openclaw-runtime-model-writer.ts index 13f4a8b4a..37d922412 100644 --- a/apps/controller/src/runtime/openclaw-runtime-model-writer.ts +++ b/apps/controller/src/runtime/openclaw-runtime-model-writer.ts @@ -58,4 +58,21 @@ export class OpenClawRuntimeModelWriter { async writeFallback(): Promise { await this.write(RUNTIME_MODEL_FALLBACK); } + + /** + * Remove the runtime-model state file so OpenClaw has no model override. + * Called when all model providers are removed (e.g. link account logout). + */ + async clear(): Promise { + const { unlink } = await import("node:fs/promises"); + try { + await unlink(this.env.openclawRuntimeModelStatePath); + logger.info( + { path: this.env.openclawRuntimeModelStatePath }, + "runtime_model_cleared", + ); + } catch (err) { + if ((err as NodeJS.ErrnoException).code !== "ENOENT") throw err; + } + } } diff --git a/apps/controller/src/services/openclaw-sync-service.ts b/apps/controller/src/services/openclaw-sync-service.ts index 3588064c1..0576eae72 100644 --- a/apps/controller/src/services/openclaw-sync-service.ts +++ b/apps/controller/src/services/openclaw-sync-service.ts @@ -312,21 +312,48 @@ export class OpenClawSyncService { } // 2. Always write files once (persistence + watcher hot-reload path). + const hasAnyProvider = + Object.keys(compiled.models?.providers ?? {}).length > 0; + + // When no model provider is configured (e.g. after link logout with no + // BYOK keys), strip the model from agents so OpenClaw cannot fall back + // to its built-in registry with the bare model name. This is a + // hot-reload-safe change (agents.list → dynamic reads, no gateway restart). + if (!hasAnyProvider) { + if (compiled.agents.defaults?.model) { + compiled.agents.defaults = { + ...compiled.agents.defaults, + model: undefined, + }; + } + for (const agent of compiled.agents.list ?? []) { + if (agent.model) { + agent.model = undefined; + } + } + } + await this.configWriter.write(compiled); await this.authProfilesWriter.writeForAgents( compiled, config.models.providers, ); this.gatewayService.noteConfigWritten(compiled); - const runtimeModelRef = resolvePrimaryModelRef( - compiled.agents.defaults?.model, - config, - compiled, - this.env, - oauthState, - ); + const runtimeModelRef = hasAnyProvider + ? resolvePrimaryModelRef( + compiled.agents.defaults?.model, + config, + compiled, + this.env, + oauthState, + ) + : null; logger.info({ seq, runtimeModelRef }, "doSync: resolved runtime model"); - await this.runtimeModelWriter.write(runtimeModelRef); + if (runtimeModelRef) { + await this.runtimeModelWriter.write(runtimeModelRef); + } else { + await this.runtimeModelWriter.clear(); + } // Write locale state for the credit-guard patch in OpenClaw runtime. // Match the controller's own locale default: unset → "en" (not "zh-CN"). const locale =