Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions apps/controller/src/runtime/openclaw-runtime-model-writer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,21 @@ export class OpenClawRuntimeModelWriter {
async writeFallback(): Promise<void> {
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<void> {
const { unlink } = await import("node:fs/promises");
try {
await unlink(this.env.openclawRuntimeModelStatePath);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Overwrite runtime model state instead of deleting it

Deleting the runtime-model state file here does not actually clear the active override while OpenClaw stays running: the nexu-runtime-model plugin’s loadState() returns its in-memory cachedState on file-read errors (including ENOENT), so the previously selected model keeps being applied after logout until process restart. In the no-provider flow this means conversations can still route with the stale model despite the sync intending to disable model usage.

Useful? React with 👍 / 👎.

logger.info(
{ path: this.env.openclawRuntimeModelStatePath },
"runtime_model_cleared",
);
} catch (err) {
if ((err as NodeJS.ErrnoException).code !== "ENOENT") throw err;
}
}
}
43 changes: 35 additions & 8 deletions apps/controller/src/services/openclaw-sync-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Comment on lines +322 to +324
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Compute config diff after mutating providerless config

The config hash check (shouldPushConfig(compiled)) happens before this new providerless-model stripping block mutates compiled, but noteConfigWritten stores the post-mutation hash. In the no-provider state, each later sync compares the unstripped compile output against the previously stored stripped hash and reports configPushed=true even when nothing changed, which causes repeated unnecessary touchAnySkillMarker() calls and incorrect push status responses.

Useful? React with 👍 / 👎.

...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 =
Expand Down
Loading