Skip to content

fix(cli): invalidate per-instance config caches on global config update - #7179

Closed
kilo-code-bot[bot] wants to merge 1 commit into
mainfrom
mark/fix-stale-config-after-global-update
Closed

fix(cli): invalidate per-instance config caches on global config update#7179
kilo-code-bot[bot] wants to merge 1 commit into
mainfrom
mark/fix-stale-config-after-global-update

Conversation

@kilo-code-bot

@kilo-code-bot kilo-code-bot Bot commented Mar 17, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Fixes stale per-instance config after Config.updateGlobal() — active instances now pick up global config changes (modes, providers, models, etc.) without requiring a server restart
  • Adds State.resetCaches() which clears all cache-only Instance.state entries across all instances without tearing down side-effectful resources

Problem

PR #7172 correctly removed Instance.disposeAll() from Config.updateGlobal() to stop it from destroying sessions, MCP connections, and in-flight operations on every config change. However, this left a gap: global.reset() only invalidates the process-wide global config lazy cache, not the per-instance Config.state (or downstream caches like Agent.state, Provider.state, etc.) that merge global config at initialization time.

This meant that after a global config change via the VS Code settings panel (e.g. changing default agent, provider, or model), the server's HTTP endpoints (/config, /app/agents, /app/providers) would continue returning stale cached data. The extension would re-fetch after the global.disposed SSE event, but get back the old values.

As noted by the review bot on #7172: comment

Fix

State.resetCaches() iterates all instance state maps and deletes entries that have no dispose callback. These are the 18 "pure cache" entries (Config, Agent, Provider, Tool, Skill, Command, Format, Plugin, etc.) that are safe to drop and will be re-computed on next access. The 9 entries with dispose callbacks (Prompt sessions, MCP connections, LSP clients, PTY processes, file watchers, etc.) are preserved.

The flow after the fix:

updateGlobal() writes config to disk
  → global.reset()           // clear global config lazy cache
  → State.resetCaches()      // clear all per-instance derived caches
  → emit "global.disposed"   // SSE to extension
  → extension re-fetches     // HTTP requests hit server
  → Config.state() re-inits  // fresh merge with updated global config
  → Agent.state() re-inits   // fresh agent list
  → UI updates correctly

Config.updateGlobal() was only resetting the global lazy cache but not
the per-instance State entries (Config.state, Agent.state, Provider.state,
etc.). This meant active instances served stale config data — e.g. after
changing default provider or model settings via the VS Code settings panel,
the server would continue returning the old values until restart.

Add State.resetCaches() which clears all cache-only State entries (those
without dispose callbacks) across all instances. This invalidates derived
state so it gets re-computed on next access, while preserving side-effectful
resources like MCP connections, sessions, and file watchers.
export function resetCaches() {
for (const entries of recordsByKey.values()) {
for (const [init, entry] of entries) {
if (entry.dispose) continue

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

WARNING: dispose is not a safe proxy for cache-only state

This also deletes long-lived per-instance stores that intentionally have no disposer, including SessionStatus.state, FileTime.state, InstructionPrompt.state, Bus.state, and Env.state. After a global config update, active sessions can lose busy status, read/write locks, subscriptions, and env overrides even though this change is meant to preserve session state.

// Provider.state, etc.) so they re-read the updated global config on next access.
// Only cache entries without dispose callbacks are cleared — side-effectful state
// like sessions, MCP connections, and file watchers are preserved.
State.resetCaches()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

WARNING: Config-backed services with disposers will not reload

State.resetCaches() only invalidates entries without a dispose callback. Services like MCP.state, LSP.state, and FileWatcher.state read Config.get() during initialization and do have disposers, so they keep running with the old global config. Changes like enabling/disabling MCP or LSP servers, or updating watcher ignore rules, will not take effect until the instance is fully disposed.

@kilo-code-bot

kilo-code-bot Bot commented Mar 17, 2026

Copy link
Copy Markdown
Contributor Author

Code Review Summary

Status: 2 Issues Found | Recommendation: Address before merge

Fix these issues in Kilo Cloud

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
packages/opencode/src/project/state.ts 40 resetCaches() treats every no-dispose entry as disposable, which also clears active runtime state like session status, file locks, bus subscriptions, and per-instance env overrides.
packages/opencode/src/config/config.ts 1561 State.resetCaches() does not recreate dispose-backed services such as MCP, LSP, and file watchers, so global config changes to those systems do not take effect.
Other Observations (not in diff)

Issues found in unchanged code that cannot receive inline comments:

None.

Files Reviewed (2 files)
  • packages/opencode/src/config/config.ts - 1 issue
  • packages/opencode/src/project/state.ts - 1 issue

Reviewed by gpt-5.4-20260305 · 638,923 tokens

@markijbema markijbema closed this Mar 17, 2026
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