Skip to content

fix(controller): clear model config when no providers configured#1080

Open
lefarcen wants to merge 4 commits intomainfrom
fix/clear-model-on-provider-logout
Open

fix(controller): clear model config when no providers configured#1080
lefarcen wants to merge 4 commits intomainfrom
fix/clear-model-on-provider-logout

Conversation

@lefarcen
Copy link
Copy Markdown
Collaborator

What

退出 link 账号后,如果没有用户自己的 BYOK provider,清除 OpenClaw 的模型配置,使 agent 无法继续回话。

Why

退出 link 后,controller 从 openclaw.json 中移除了 models.providers,UI 正确显示"没有配置模型"。但 agents.defaults.model.primary 仍然保留了一个裸模型名(如 nexu-chat),OpenClaw 通过内置 registry 解析这个裸名,继续正常回话——与 UI 状态不一致。

How

When no model providers are configured (no BYOK keys, no link, no litellm):

  1. Strip model from compiled config — remove agents.defaults.model and per-agent model from openclaw.json before writing. This is hot-reload safe (agents.list → dynamic reads, no gateway restart).
  2. Delete nexu-runtime-model.json — remove the runtime model state file so OpenClaw has no model override.

When providers exist (BYOK or link), behavior is completely unchanged.

Affected areas

  • Controller (backend / API)

Checklist

  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes
  • pnpm generate-types run (if API routes/schemas changed) — N/A

The version was accidentally downgraded to 0.1.10 for local nightly
update testing and merged to main. Restore to 0.1.11.
After logging out of link with no BYOK providers, the controller
still wrote a bare model name (e.g. "nexu-chat") into both
openclaw.json and nexu-runtime-model.json. OpenClaw resolved this
bare name via its built-in registry and continued serving requests,
even though the UI correctly showed no model configured.

When no model providers are configured:
- Strip agents.defaults.model and per-agent model from the compiled
  config before writing openclaw.json (hot-reload safe, no gateway
  restart)
- Delete nexu-runtime-model.json so OpenClaw has no model override

When providers exist (BYOK or link), behavior is unchanged.
@sentry
Copy link
Copy Markdown

sentry bot commented Apr 13, 2026

Codecov Report

❌ Patch coverage is 0% with 41 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...s/controller/src/services/openclaw-sync-service.ts 0.00% 29 Missing ⚠️
...oller/src/runtime/openclaw-runtime-model-writer.ts 0.00% 12 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2c8c70e665

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

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 👍 / 👎.

Comment on lines +322 to +324
if (!hasAnyProvider) {
if (compiled.agents.defaults?.model) {
compiled.agents.defaults = {
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 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant