{"record":{"id":"257bd05d5581cc33","repo":"can1357/oh-my-pi","slug":"failed-to-stop-lsp-server-s-with-superseded-confi","errorCode":null,"errorMessage":"Failed to stop LSP server(s) with superseded configuration: ${failed.map(client => client.config.command).join(\", \")}","messagePattern":"Failed to stop LSP server\\(s\\) with superseded configuration: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/lsp/client.ts","lineNumber":911,"sourceCode":"\t\t\t}\n\t\t}\n\t\tawait Promise.all(\n\t\t\tstalePending.map(async ([, pending]) => {\n\t\t\t\ttry {\n\t\t\t\t\tawait untilAborted(signal, pending.promise);\n\t\t\t\t} catch {\n\t\t\t\t\tthrowIfAborted(signal);\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\n\t\tconst stale = Array.from(clients.values()).filter(\n\t\t\tclient => path.resolve(client.cwd) === resolvedCwd && !fresh.has(client.name),\n\t\t);\n\t\tconst results = await Promise.all(stale.map(client => shutdownClientInstance(client)));\n\t\tconst failed = stale.filter((_client, index) => results[index] !== true);\n\t\tif (failed.length > 0) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Failed to stop LSP server(s) with superseded configuration: \" +\n\t\t\t\t\tfailed.map(client => client.config.command).join(\", \"),\n\t\t\t);\n\t\t}\n\t\treturn stale.map(client => client.config.command);\n\t})();\n\tclientReloadBarriers.set(resolvedCwd, cleanup);\n\tvoid cleanup.then(\n\t\t() => {\n\t\t\tif (clientReloadBarriers.get(resolvedCwd) === cleanup) clientReloadBarriers.delete(resolvedCwd);\n\t\t},\n\t\t() => {},\n\t);\n\treturn cleanup;\n}\n\n/** Allow an explicit user reload to retry a matching initialization failure immediately. */\nexport function clearInitializationFailure(config: ServerConfig, cwd: string): void {","sourceCodeStart":893,"sourceCodeEnd":929,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/lsp/client.ts#L893-L929","documentation":"When LSP configuration changes, clients whose cwd matches but whose names are no longer in the fresh config set are considered stale and are shut down. If any shutdown fails (shutdownClientInstance returned false), the operation throws listing the server commands that could not be stopped, so callers don't silently leave zombie servers.","triggerScenarios":"Reloading/changing LSP config where a previously running server's shutdown request times out, the process ignores SIGTERM/kill, or the server already crashed in a wedged state.","commonSituations":"A hung language server (e.g. typescript-language-server busy), insufficient permissions to kill the child process, config edit that renames a server while its old instance is unresponsive.","solutions":["Manually kill the listed server processes (the message names each command).","Retry the config reload once the server responds; hung servers often recover or stay dead.","Check server logs for why shutdown was refused.","If a server reliably hangs on shutdown, disable or upgrade that specific LSP server binary."],"exampleFix":"// before\nawait lspClientRegistry.applyConfig(freshConfig); // throws listing stale servers\n// after\ntry {\n  await lspClientRegistry.applyConfig(freshConfig);\n} catch (e) {\n  if (e.message.includes(\"superseded configuration\")) {\n    for (const cmd of e.message.split(\": \")[1]?.split(\", \") ?? []) await killCommand(cmd);\n  }\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"// before reloading, confirm the listed servers respond to a health ping\nconst healthy = await Promise.all(servers.map(s => pingLspServer(s).then(() => true).catch(() => false)));\nif (healthy.some(h => !h)) logger.warn(\"some LSP servers are unresponsive; shutdown may fail\");","typeGuard":null,"tryCatchPattern":"try {\n  await registry.applyConfig(freshConfig);\n} catch (e) {\n  if (e.message.includes(\"superseded configuration\")) {\n    const commands = e.message.split(\": \").slice(1).join(\": \").split(\", \");\n    logger.error(\"LSP shutdown failed; killing manually\", { commands });\n    await Promise.all(commands.map(cmd => killCommand(cmd)));\n  } else throw e;\n}","preventionTips":["Restart unresponsive language servers proactively","Set and honor shutdown timeouts in LSP server config","Watch for hung ts-language-server processes after config edits","Verify child-process kill permissions in restricted environments"],"tags":["lsp","process-management","shutdown","timeout"],"backgroundTag":"process-shutdown-failed","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}