openclaw - 💡(How to fix) Fix markAuthProfileSuccess rewrites auth-profiles.json on every success, churning the models.json resolution cache

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

markAuthProfileSuccess persists the auth-profile store (auth-profiles.json) on every successful model call — even when the only change is the lastUsed timestamp. Because buildModelsJsonFingerprint folds auth-profiles.json's mtime into the models.json resolution cache key, this redundant write invalidates the model cache on every message, forcing a full model re-resolution per message for plugin-backed providers.

Observed on [email protected] (and identical on 2026.5.27).

Root Cause

markAuthProfileSuccess persists the auth-profile store (auth-profiles.json) on every successful model call — even when the only change is the lastUsed timestamp. Because buildModelsJsonFingerprint folds auth-profiles.json's mtime into the models.json resolution cache key, this redundant write invalidates the model cache on every message, forcing a full model re-resolution per message for plugin-backed providers.

Fix Action

Fix / Workaround

A downstream build-time patch implementing exactly this (return false from the updater on a lastUsed-only delta) eliminates the per-message re-resolution; happy to send a PR if useful.

Code Example

model-resolution = 6304 ms
model-resolution = 12873 ms
model-resolution = 12473 ms
RAW_BUFFERClick to expand / collapse

Summary

markAuthProfileSuccess persists the auth-profile store (auth-profiles.json) on every successful model call — even when the only change is the lastUsed timestamp. Because buildModelsJsonFingerprint folds auth-profiles.json's mtime into the models.json resolution cache key, this redundant write invalidates the model cache on every message, forcing a full model re-resolution per message for plugin-backed providers.

Observed on [email protected] (and identical on 2026.5.27).

Mechanism

  1. The model cache key includes auth-profiles.json's mtime. buildModelsJsonFingerprint (in dist/models-config-*.js) computes authProfilesMtimeMs = await readFileMtimeMs(path.join(agentDir, "auth-profiles.json")) and folds it into the MODELS_JSON_STATE.readyCache key.

  2. A successful call rewrites auth-profiles.json unconditionally. markAuthProfileSuccess (in dist/profiles-*.js) stamps lastUsed = Date.now() and calls updateAuthProfileStoreWithLock with an updater that returns true whenever the profile exists — so saveAuthProfileStore runs and the file mtime ticks, even when nothing but lastUsed changed.

  3. Next message → fresh mtime → fingerprint differs → cache miss → ensureOpenClawModelsJson re-resolves the full model set (runs the provider's prepareProviderDynamicModel hook and rewrites models.json).

The embedded-run path tries the cheap resolveModelAsync({ skipPiDiscovery: true }) first and only falls through to the expensive ensureOpenClawModelsJson when that returns null. Core providers (OpenAI/Anthropic) resolve on the cheap attempt and never see the symptom; plugin-backed providers (e.g. Kimi) return null and hit the expensive path on every message.

Impact

For a plugin-backed provider whose model resolution re-queries the provider API on a cache miss (e.g. Kimi), this adds 6–13 s of latency per message, trending up rather than warming — measured across sequential messages on a steady single-profile tenant:

model-resolution = 6304 ms
model-resolution = 12873 ms
model-resolution = 12473 ms

The cache never holds because each successful turn re-bumps the auth-profiles.json mtime.

Reproduction

  1. Configure a tenant with a plugin-backed provider (Kimi, or any provider whose resolveModelAsync({skipPiDiscovery:true}) returns null).
  2. Send several sequential messages through the running gateway.
  3. Observe auth-profiles.json's mtime change after every successful turn, and model-resolution re-running (not cache-hitting) each message.

Proposed fix

Skip the store save in markAuthProfileSuccess when the only delta is lastUsed — i.e. have the locked updater return false when lastGood is unchanged for this profile and no other usage-stat field changed. Continue writing whenever lastGood changes or failure/cooldown state must be cleared.

This is safe: usageStats.lastUsed is write-only in the dist — nothing reads it for model selection, failover, or rotation (the only lastUsedAt reads belong to an unrelated in-memory model cache). Suppressing the no-op write keeps the auth-profiles.json mtime stable in steady state, so the models.json fingerprint cache holds and real auth changes (profile add/remove, lastGood / credential refresh) still invalidate it correctly.

A downstream build-time patch implementing exactly this (return false from the updater on a lastUsed-only delta) eliminates the per-message re-resolution; happy to send a PR if useful.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - 💡(How to fix) Fix markAuthProfileSuccess rewrites auth-profiles.json on every success, churning the models.json resolution cache