From 3378b51c2d38cebb3f1b5dddce41e3dfabeeda60 Mon Sep 17 00:00:00 2001 From: Adam Hulme Date: Wed, 15 Jul 2026 14:45:12 +0100 Subject: [PATCH 1/2] Configure Claude launch permission mode --- README.md | 2 ++ package.json | 21 +++++++++++++++++++++ src/agentCommand.ts | 22 ++++++++++++++++++++++ src/sessionManager.ts | 19 ++++++++++++++----- test/agentCommand.test.ts | 17 +++++++++++++++++ 5 files changed, 76 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9072963..e941cb8 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,8 @@ All shortcuts can be changed in Keyboard Shortcuts. The most common settings are: - `lookout.codex.command` and `lookout.claude.command` — provider launch commands; +- `lookout.claude.permissionMode` — permission mode for Lookout-launched Claude + sessions (`auto` by default); an explicit command flag takes precedence; - `lookout.codex.enabled` and `lookout.claude.enabled` — entries shown in the new-agent picker; - `lookout.codex.lifecycleIntegration` and diff --git a/package.json b/package.json index 5432957..9d2fa72 100644 --- a/package.json +++ b/package.json @@ -839,6 +839,27 @@ "default": "claude", "description": "Command used to launch a Claude Code session." }, + "lookout.claude.permissionMode": { + "type": "string", + "enum": [ + "auto", + "acceptEdits", + "manual", + "dontAsk", + "plan", + "inherit" + ], + "enumDescriptions": [ + "Let Claude automatically classify whether each action needs approval.", + "Automatically approve file edits while prompting for other protected actions.", + "Ask before protected actions.", + "Deny actions that require permission instead of asking.", + "Restrict Claude to planning without making changes.", + "Do not pass a permission mode; use Claude's resolved default." + ], + "default": "auto", + "description": "Permission mode for Claude Code sessions launched by Lookout. An explicit --permission-mode in the launch command takes precedence." + }, "lookout.claude.enabled": { "type": "boolean", "default": true, diff --git a/src/agentCommand.ts b/src/agentCommand.ts index 80ad771..06a1222 100644 --- a/src/agentCommand.ts +++ b/src/agentCommand.ts @@ -19,6 +19,14 @@ export type LaunchShell = | 'argv' | 'unknown'; +export type ClaudePermissionMode = + | 'inherit' + | 'auto' + | 'acceptEdits' + | 'manual' + | 'dontAsk' + | 'plan'; + export function classifyShell( shellPath: string | undefined, _platform: NodeJS.Platform = process.platform @@ -191,6 +199,20 @@ export function isDirectAgentCommand( ); } +export function withClaudePermissionMode( + command: string, + mode: ClaudePermissionMode +): string { + if ( + mode === 'inherit' || + !isDirectAgentCommand(command, 'claude') || + /(^|\s)--permission-mode(?:\s|=)/.test(command) + ) { + return command; + } + return `${command} --permission-mode ${mode}`; +} + export function shellQuote(value: string, shell: LaunchShell): string { switch (shell) { case 'argv': diff --git a/src/sessionManager.ts b/src/sessionManager.ts index 9372149..487ce91 100644 --- a/src/sessionManager.ts +++ b/src/sessionManager.ts @@ -11,8 +11,10 @@ import { isDirectAgentCommand, PROVIDER_ACTIVITY_TOOL_MATCHER, shellQuote, + withClaudePermissionMode, withCodexLifecycleIntegration, withCodexTokenBudget, + type ClaudePermissionMode, type LaunchShell } from './agentCommand'; import { captureGitBaseline, listUncommittedChanges } from './gitReview'; @@ -1335,6 +1337,13 @@ export class SessionManager implements vscode.Disposable { tokenBudget.limitTokens, launchShell ); + } else if (request.kind === 'claude') { + command = withClaudePermissionMode( + command, + vscode.workspace + .getConfiguration('lookout.claude') + .get('permissionMode', 'auto') + ); } const notifyHelperPath = path.join( this.context.extensionPath, @@ -1366,8 +1375,8 @@ export class SessionManager implements vscode.Disposable { } if ( request.kind !== 'claude' || - /(^|\s)--settings(?:\s|=)/.test(request.command) || - !isDirectClaudeCommand(request.command) + /(^|\s)--settings(?:\s|=)/.test(command) || + !isDirectClaudeCommand(command) ) { return { command, @@ -1385,11 +1394,11 @@ export class SessionManager implements vscode.Disposable { .getConfiguration('lookout.claude') .get('lifecycleIntegration', true); if (!statusLineIntegration && !lifecycleIntegration) { - return { command: request.command, integrationsSkipped: false }; + return { command, integrationsSkipped: false }; } if (launchShell === 'unknown') { // No known-safe way to quote the settings path for this shell. - return { command: request.command, integrationsSkipped: true }; + return { command, integrationsSkipped: true }; } await vscode.workspace.fs.createDirectory(this.context.globalStorageUri); const helperPath = path.join( @@ -1476,7 +1485,7 @@ export class SessionManager implements vscode.Disposable { Buffer.from(JSON.stringify(settings), 'utf8') ); return { - command: `${request.command} --settings ${shellQuote( + command: `${command} --settings ${shellQuote( settingsUri.fsPath, launchShell )}`, diff --git a/test/agentCommand.test.ts b/test/agentCommand.test.ts index 44dfc8a..281ec90 100644 --- a/test/agentCommand.test.ts +++ b/test/agentCommand.test.ts @@ -4,6 +4,7 @@ import { classifyShell, isDirectAgentCommand, shellQuote, + withClaudePermissionMode, withCodexLifecycleIntegration, withCodexTokenBudget } from '../src/agentCommand'; @@ -104,6 +105,22 @@ test('recognizes direct provider commands without accepting shell expressions', assert.equal(shellQuote("it's ready", 'posix'), "'it'\\''s ready'"); }); +test('starts direct Claude sessions in the configured permission mode', () => { + assert.equal( + withClaudePermissionMode('claude --model opus', 'auto'), + 'claude --model opus --permission-mode auto' + ); + assert.equal( + withClaudePermissionMode('claude --permission-mode manual', 'auto'), + 'claude --permission-mode manual' + ); + assert.equal(withClaudePermissionMode('claude', 'inherit'), 'claude'); + assert.equal( + withClaudePermissionMode('wrapper claude', 'auto'), + 'wrapper claude' + ); +}); + test('classifies terminal shells from their executable paths', () => { assert.equal(classifyShell('/bin/bash', 'linux'), 'posix'); assert.equal(classifyShell('/usr/local/bin/fish', 'darwin'), 'posix'); From 76548953652620e4bd38633743a68135dfd8bfe0 Mon Sep 17 00:00:00 2001 From: Adam Hulme Date: Wed, 15 Jul 2026 14:54:52 +0100 Subject: [PATCH 2/2] Document Claude permission mode --- AGENTS.md | 6 ++++++ CHANGELOG.md | 3 +++ CLAUDE.md | 6 ++++++ docs/TESTPLAN.txt | 7 +++++++ 4 files changed, 22 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 003ab63..723771f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,3 +3,9 @@ - Run every GitHub CLI (`gh`) command outside the sandbox. The sandbox cannot access the host keyring and can incorrectly report that authentication is invalid. +- Keep user-facing documentation aligned with behavior changes. Update the + Unreleased section of `CHANGELOG.md` for user-visible changes, update + `README.md` when commands, settings, requirements, or workflows change, and + extend `docs/TESTPLAN.txt` when new behavior needs interactive release + verification. Record changes to durable design decisions in + `docs/DECISIONS.md`. diff --git a/CHANGELOG.md b/CHANGELOG.md index d7e7aa1..9d80f7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## 1.0.0 — Unreleased +- Add a configurable permission mode for Claude sessions launched by Lookout, + defaulting to automatic permission classification while preserving explicit + launch-command overrides and an option to inherit Claude's resolved default. - Clear stale attention indicators as soon as updates are read, prefer unread activity during navigation, synchronize the newest Claude usage observation across windows, and discard quota windows after their reset time. diff --git a/CLAUDE.md b/CLAUDE.md index 7ea0dee..b7b3480 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -63,3 +63,9 @@ Authoritative sources only, never estimated: Codex via `codex app-server` JSON-R - `docs/ROADMAP.md`, `docs/RESEARCH.md` — roadmap and research record - `docs/plans/`, `docs/research/` — Workshop-convention artifacts - `docs/sessions/` — dated session checkpoints; the interactive smoke matrix (`docs/sessions/2026-07-10-smoke.md`) is the gating milestone before Marketplace release + +Keep documentation in the same change as the behavior it describes. Add +user-visible changes to the Unreleased section of `CHANGELOG.md`; update +`README.md` for changed commands, settings, requirements, or workflows; extend +`docs/TESTPLAN.txt` when interactive release verification is needed; and update +`docs/DECISIONS.md` whenever a durable design decision changes. diff --git a/docs/TESTPLAN.txt b/docs/TESTPLAN.txt index 5c51849..5b3e76e 100644 --- a/docs/TESTPLAN.txt +++ b/docs/TESTPLAN.txt @@ -49,6 +49,13 @@ B. LAUNCH AND LAYOUT (~10 min) leaving the editor area free for code and review. The Agents toolbar has no permanent isolated-worktree action, and Launch from Template is absent until at least one template is configured. + 5a. Claude permission mode: with lookout.claude.permissionMode at its default, + launch Claude and confirm /permissions reports auto mode. Set the setting + to manual, launch a new Claude session, and confirm manual mode. Restore + the setting to auto, set lookout.claude.command to + "claude --permission-mode plan", and confirm the explicit plan mode wins. + Finally set permissionMode to inherit, restore the command to "claude", + launch again, and confirm Lookout does not force a mode. Restore defaults. 6. Terminal tab names read "Lookout: