{"record":{"id":"dbdf195e6ec14b65","repo":"can1357/oh-my-pi","slug":"lsp-action-action-is-disabled-in-this-read-only","errorCode":null,"errorMessage":"LSP action ${action} is disabled in this read-only session","messagePattern":"LSP action (.+?) is disabled in this read-only session","errorType":"exception","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/lsp/tool.ts","lineNumber":209,"sourceCode":"\n\tconstructor(private readonly session: ToolSession) {\n\t\tthis.description = prompt.render(lspDescription);\n\t}\n\n\tstatic createIf(session: ToolSession): LspTool | null {\n\t\treturn session.enableLsp === false ? null : new LspTool(session);\n\t}\n\n\tasync execute(\n\t\t_toolCallId: string,\n\t\tparams: LspParams,\n\t\tsignal?: AbortSignal,\n\t\t_onUpdate?: AgentToolUpdateCallback<LspToolDetails>,\n\t\t_context?: AgentToolContext,\n\t): Promise<AgentToolResult<LspToolDetails>> {\n\t\tconst { action, file, line, symbol, query, new_name, apply, timeout } = params;\n\t\tif (this.session.lspReadOnly && !LSP_READONLY_ACTIONS.has(action)) {\n\t\t\tthrow new ToolError(`LSP action ${action} is disabled in this read-only session`);\n\t\t}\n\t\tconst timeoutSec = clampTimeout(\"lsp\", timeout, this.session.settings.get(\"tools.maxTimeout\"));\n\t\tconst timeoutSignal = AbortSignal.timeout(timeoutSec * 1000);\n\t\tconst callerSignal = signal;\n\t\tsignal = callerSignal ? AbortSignal.any([callerSignal, timeoutSignal]) : timeoutSignal;\n\t\tthrowIfAborted(signal);\n\n\t\tconst config = getConfig(this.session.cwd);\n\n\t\t// Status action doesn't need a file\n\t\tif (action === \"status\") {\n\t\t\tconst configuredNames = Object.keys(config.servers);\n\t\t\tconst lspmuxState = await detectLspmux();\n\t\t\tconst lspmuxStatus = lspmuxState.available\n\t\t\t\t? lspmuxState.running\n\t\t\t\t\t? \"lspmux: active (multiplexing enabled)\"\n\t\t\t\t\t: \"lspmux: installed but server not running\"\n\t\t\t\t: \"\";","sourceCodeStart":191,"sourceCodeEnd":227,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/lsp/tool.ts#L191-L227","documentation":"The LSP agent tool blocks any action not in the read-only allowlist (diagnostics, definition, references, hover, symbols, status, capabilities, …) when the session is in lspReadOnly mode. This guards read-only agent sessions from mutating operations such as rename or apply of workspace edits.","triggerScenarios":"Calling execute() with an action like `rename`, `apply`, or another write-tier action while this.session.lspReadOnly is true — typically a subagent or session configured with read-only LSP access.","commonSituations":"Running an agent in restricted/read-only mode but prompting it to perform a rename; a subagent inheriting a read-only session attempting workspace edits; automation scripts using a hardened session for refactoring.","solutions":["Use a session with lspReadOnly disabled if mutation is intended.","Restrict the task to read-only actions (hover, references, definition, diagnostics).","Perform the mutation (e.g. rename) outside the restricted session via an editor or a non-read-only tool session.","If this is your own integration, enable the write capability explicitly when constructing the LSP tool session."],"exampleFix":"// before\nawait lspTool.execute({ action: \"rename\", file: \"a.ts\", line: 3, symbol: \"foo\", new_name: \"bar\" }); // read-only session\n// after\nconst writableSession = createSession({ lspReadOnly: false });\nawait writableSession.lsp.execute({ action: \"rename\", file: \"a.ts\", line: 3, symbol: \"foo\", new_name: \"bar\" });","handlingStrategy":"validation","validationCode":"const READ_ONLY = new Set([\"diagnostics\",\"definition\",\"type_definition\",\"implementation\",\"references\",\"hover\",\"symbols\",\"status\",\"capabilities\"]);\nif (isReadOnlySession && !READ_ONLY.has(action)) throw new Error(`Action ${action} unavailable in read-only session`);","typeGuard":"function isReadOnlyAction(action: string): boolean {\n  return !READ_ONLY_ACTIONS.has(action);\n}","tryCatchPattern":"try {\n  await lspTool.execute({ action: \"rename\", /* ... */ });\n} catch (err) {\n  if (err instanceof ToolError && err.message.includes(\"read-only session\")) {\n    // degrade: report to the agent that mutation is disabled\n  } else throw err;\n}","preventionTips":["Check session capabilities before prompting an agent to mutate code.","Keep write actions out of read-only subagent prompts.","Encode allowed actions in your agent's system prompt/planner."],"tags":["lsp","permissions","read-only","tool"],"backgroundTag":"operation-not-permitted","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}