can1357/oh-my-pi · error · ToolError

LSP action ${action} is disabled in this read-only session

Error message

LSP action ${action} is disabled in this read-only session

What it means

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.

Source

Thrown at packages/coding-agent/src/lsp/tool.ts:209

	constructor(private readonly session: ToolSession) {
		this.description = prompt.render(lspDescription);
	}

	static createIf(session: ToolSession): LspTool | null {
		return session.enableLsp === false ? null : new LspTool(session);
	}

	async execute(
		_toolCallId: string,
		params: LspParams,
		signal?: AbortSignal,
		_onUpdate?: AgentToolUpdateCallback<LspToolDetails>,
		_context?: AgentToolContext,
	): Promise<AgentToolResult<LspToolDetails>> {
		const { action, file, line, symbol, query, new_name, apply, timeout } = params;
		if (this.session.lspReadOnly && !LSP_READONLY_ACTIONS.has(action)) {
			throw new ToolError(`LSP action ${action} is disabled in this read-only session`);
		}
		const timeoutSec = clampTimeout("lsp", timeout, this.session.settings.get("tools.maxTimeout"));
		const timeoutSignal = AbortSignal.timeout(timeoutSec * 1000);
		const callerSignal = signal;
		signal = callerSignal ? AbortSignal.any([callerSignal, timeoutSignal]) : timeoutSignal;
		throwIfAborted(signal);

		const config = getConfig(this.session.cwd);

		// Status action doesn't need a file
		if (action === "status") {
			const configuredNames = Object.keys(config.servers);
			const lspmuxState = await detectLspmux();
			const lspmuxStatus = lspmuxState.available
				? lspmuxState.running
					? "lspmux: active (multiplexing enabled)"
					: "lspmux: installed but server not running"
				: "";

View on GitHub (pinned to 9690622007)

Solutions

  1. Use a session with lspReadOnly disabled if mutation is intended.
  2. Restrict the task to read-only actions (hover, references, definition, diagnostics).
  3. Perform the mutation (e.g. rename) outside the restricted session via an editor or a non-read-only tool session.
  4. If this is your own integration, enable the write capability explicitly when constructing the LSP tool session.

Example fix

// before
await lspTool.execute({ action: "rename", file: "a.ts", line: 3, symbol: "foo", new_name: "bar" }); // read-only session
// after
const writableSession = createSession({ lspReadOnly: false });
await writableSession.lsp.execute({ action: "rename", file: "a.ts", line: 3, symbol: "foo", new_name: "bar" });
Defensive patterns

Strategy: validation

Validate before calling

const READ_ONLY = new Set(["diagnostics","definition","type_definition","implementation","references","hover","symbols","status","capabilities"]);
if (isReadOnlySession && !READ_ONLY.has(action)) throw new Error(`Action ${action} unavailable in read-only session`);

Type guard

function isReadOnlyAction(action: string): boolean {
  return !READ_ONLY_ACTIONS.has(action);
}

Try / catch

try {
  await lspTool.execute({ action: "rename", /* ... */ });
} catch (err) {
  if (err instanceof ToolError && err.message.includes("read-only session")) {
    // degrade: report to the agent that mutation is disabled
  } else throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/dbdf195e6ec14b65. Report an issue: GitHub.