{"record":{"id":"0db95c2addcfcda1","repo":"can1357/oh-my-pi","slug":"lsp-configuration-was-superseded-during-reload","errorCode":null,"errorMessage":"LSP configuration was superseded during reload: ${config.command}","messagePattern":"LSP configuration was superseded during reload: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/coding-agent/src/lsp/client.ts","lineNumber":963,"sourceCode":"\tcwd: string,\n\tinitTimeoutMs?: number,\n\tsignal?: AbortSignal,\n): Promise<LspClient> {\n\tconst key = clientKey(config, cwd);\n\t// Check if client already exists\n\tconst existingClient = clients.get(key);\n\tif (existingClient && !invalidatedClientKeys.has(key)) {\n\t\texistingClient.lastActivity = Date.now();\n\t\treturn existingClient;\n\t}\n\n\t// Check if another coroutine is already creating this client\n\tconst existingLock = clientLocks.get(key);\n\tif (existingLock) {\n\t\treturn existingLock.promise;\n\t}\n\tif (invalidatedClientKeys.has(key)) {\n\t\tthrow new Error(`LSP configuration was superseded during reload: ${config.command}`);\n\t}\n\n\t// Do not start a fresh identity until superseded processes are confirmed stopped.\n\tconst reloadBarrier = clientReloadBarriers.get(path.resolve(cwd));\n\tif (reloadBarrier) {\n\t\ttry {\n\t\t\tawait untilAborted(signal, reloadBarrier);\n\t\t} catch (error) {\n\t\t\tthrowIfAborted(signal);\n\t\t\tthrow error;\n\t\t}\n\t\tconst clientAfterReload = clients.get(key);\n\t\tif (clientAfterReload && !invalidatedClientKeys.has(key)) {\n\t\t\tclientAfterReload.lastActivity = Date.now();\n\t\t\treturn clientAfterReload;\n\t\t}\n\t\tconst lockAfterReload = clientLocks.get(key);\n\t\tif (lockAfterReload) return lockAfterReload.promise;","sourceCodeStart":945,"sourceCodeEnd":981,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/lsp/client.ts#L945-L981","documentation":"During LSP client creation, getConfigOrCreate checks whether the client configuration for this key was invalidated by a concurrent reload. If a reload superseded the config while this coroutine was waiting (past the lock check), creating a client from the stale config could launch a server with outdated settings, so the library throws instead.","triggerScenarios":"Two concurrent callers race: caller A starts client creation, a config reload invalidates the key (invalidatedClientKeys), then A resumes past the clientLocks check and finds its key invalidated — the throw fires at the pre-lock recheck.","commonSituations":"Editing LSP server config (command, args) in .omp settings while a first tool call triggers server startup; hot-reloading config during warmup; multiple files opened simultaneously right after a config change.","solutions":["Retry the operation — the next call will create the client from the fresh config","Ensure config reloads don't race with first LSP usage (await reload completion before tool calls)","If it recurs, check for repeated config-watch triggers (e.g. a file being rewritten in a loop)"],"exampleFix":"// before: fire tool call immediately after editing config\nawait updateConfig(); await lspHover(file, pos);\n// after: await reload settling, then retry on supersession\nawait updateConfig(); await lspReloadSettled();\ntry { await lspHover(file, pos); } catch (e) { if (String(e).includes('superseded')) await lspHover(file, pos); }","handlingStrategy":"retry","validationCode":"// No public pre-check for internal invalidated keys; best effort is ensuring config is stable\nif (configWatcher.pendingReload) await configWatcher.settled();","typeGuard":null,"tryCatchPattern":"try {\n  const client = await getConfigOrCreate(config, cwd);\n} catch (err) {\n  if (String(err.message).includes('superseded during reload')) {\n    await Bun.sleep(50); // let reload finish, then retry with fresh config\n    return getConfigOrCreate(config, cwd);\n  }\n  throw err;\n}","preventionTips":["Await config reload events before issuing LSP-dependent tool calls","Debounce rapid config edits so reloads don't interleave with startup","Retry on 'superseded' errors — they are transient race artifacts"],"tags":["lsp","race-condition","config-reload"],"backgroundTag":"lsp-config-superseded-during-reload","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}