Skip to content

fix(acp): align permission flow across clients#2690

Merged
Mingholy merged 7 commits intoQwenLM:mainfrom
LaZzyMan:fix/acp-permission-flow
Mar 27, 2026
Merged

fix(acp): align permission flow across clients#2690
Mingholy merged 7 commits intoQwenLM:mainfrom
LaZzyMan:fix/acp-permission-flow

Conversation

@LaZzyMan
Copy link
Copy Markdown
Collaborator

@LaZzyMan LaZzyMan commented Mar 26, 2026

TLDR

Align ACP permission handling across CLI and VS Code by moving auto-approval decisions fully into the shared core permission flow, removing VS Code-side auto-allow behavior, and fixing shell permission UX for compound commands and always-allow labels.

Screenshots / Video Demo

N/A — this PR is mainly a permission-flow and prompt-option correctness fix. The user-facing changes are in approval behavior and option labels rather than a new visual feature.

Dive Deeper

This PR consolidates ACP permission behavior around the same L3→L4→L5 flow already used by the core scheduler:

  • L3: tool default permission
  • L4: PermissionManager override
  • L5: approval mode override

Key changes:

  • added shared permission helpers so CLI ACP sessions and the core scheduler evaluate and persist permission outcomes the same way
  • made PermissionManager shell evaluation async so shell defaults resolve from AST analysis instead of leaking unresolved default decisions
  • reloaded settings from the active cwd when persisting permission rules so ACP-created rules land in the correct settings scope
  • removed VS Code webview-side auto approval in auto-edit / yolo, leaving permission policy to the backend instead of the UI layer
  • kept edit/write diff suppression in auto modes to avoid opening IDE diffs that should not appear
  • extracted ACP permission option/content generation into shared session utilities and wired answer payloads through subagent approval responses
  • fixed exec always-allow labels to reflect actual persisted shell rules like Bash(git add *) instead of only showing the root binary
  • fixed compound shell confirmation details so already-allowed subcommands are filtered out and remaining subcommands can still offer always-allow when no explicit ask rule matched
  • narrowed pmForcedAsk behavior so only explicit ask rules suppress always-allow options
  • added focused coverage for session permission behavior, subagent approval payloads, shell confirmation scoping, and explicit ask-rule detection

Reviewer Test Plan

  1. In VS Code ACP mode, switch to auto-edit and trigger an ask-required shell command such as git add file.
    • Confirm it no longer gets auto-approved by the webview layer.
    • Confirm no incorrect Bash(git add *) rule is silently persisted just because the frontend chose an allow-always option.
  2. In VS Code ACP mode, run git add file && git commit -m "msg" with an existing allow rule for Bash(git add *).
    • Confirm the prompt still offers always-allow for the remaining git commit scope.
    • Confirm the displayed always-allow label matches the actual persisted rule scope.
  3. In CLI mode, verify edit/write flows in auto-edit and yolo do not open IDE diffs.
  4. Sanity check ACP session load/new-session behavior still works after the config-loading changes.

Testing Matrix

🍏 🪟 🐧
npm run ✅ bundle
npx
Docker
Podman - -
Seatbelt - -

Additional validation:

  • npm run build -w packages/vscode-ide-companion
  • focused Vitest run for ACP/session/shell permission coverage (158 passed, 0 failed)

Linked issues / bugs

#2640
#2673
#2700

- glob: compute relativePaths relative to projectRoot (config.getTargetDir())
  instead of searchDir so that FileDiscoveryService evaluates .gitignore /
  .qwenignore rules against the correct paths when path option is used or
  when searching across multiple workspace directories

- grep: deduplicate rawMatches by filePath:lineNumber key when searching
  multiple workspace directories to prevent duplicate results from
  overlapping search roots (e.g. parent dir + child dir in workspace)

- ripGrep: deduplicate output lines by filepath:linenum prefix when
  searching multiple workspace paths to handle the same edge case

- tests: add regression tests covering
  - glob ignores files matching .gitignore/.qwenignore when path option
    points to a subdirectory
  - grep deduplication with parent+child overlapping workspace dirs
  - ripgrep deduplication when raw output contains duplicate lines
@github-actions
Copy link
Copy Markdown
Contributor

📋 Review Summary

This PR aligns ACP (Agent Client Protocol) permission handling across the CLI and VS Code extension by consolidating permission decisions into the shared core permission flow. The implementation removes VS Code webview-side auto-approval behavior, fixes shell permission UX for compound commands and always-allow labels, and introduces shared permission helpers to ensure consistent L3→L4→L5 permission evaluation across all clients.

🔍 General Feedback

  • Strong architectural alignment: The L3→L4→L5 permission flow (Tool Default → PermissionManager → ApprovalMode) is now consistently applied across CLI, VS Code ACP, and subagent contexts
  • Excellent test coverage: 158 tests passed with comprehensive coverage for permission scenarios, compound commands, and edge cases
  • Good separation of concerns: Permission logic is properly extracted into shared utilities (permission-helpers.ts, permissionUtils.ts)
  • Security-conscious: Command substitution detection and AST-based read-only analysis are properly integrated into the permission flow

🎯 Specific Feedback

🟡 High

  • File: packages/core/src/permissions/permission-manager.ts:158-175 - The evaluate method now returns Promise<PermissionDecision> but the change from sync to async may have downstream impacts on callers that weren't fully audited. While the diff shows proper await usage in tested paths, verify all call sites handle the async nature correctly.

  • File: packages/cli/src/acp-integration/session/Session.ts:637-703 - The permission flow implementation is complex with multiple early-return paths (earlyErrorResponse). Consider extracting the L3→L4→L5 flow into a separate helper function similar to what was done in coreToolScheduler.ts to improve readability and testability.

🟢 Medium

  • File: packages/core/src/core/permission-helpers.ts:109-129 - The evaluatePermissionRules function checks pm.hasMatchingAskRule(pmCtx) only when pmDecision === 'ask'. This is correct, but the comment could be clearer about why this specific check matters (i.e., to distinguish between explicit ask rules vs. default fallback to ask for non-readonly commands).

  • File: packages/cli/src/acp-integration/session/permissionUtils.ts:47-62 - The formatExecPermissionScopeLabel function extracts Bash rules using regex. Consider making this more robust by using the existing parseRule function from rule-parser.ts instead of manual regex parsing to ensure consistency.

  • File: packages/core/src/tools/shell.ts:134-165 - The filtering of already-allowed sub-commands in getConfirmationDetails is a great UX improvement, but the error handling for pm.isCommandAllowed failures could be more explicit about what happens when the PermissionManager throws (currently just logs and includes the command).

🔵 Low

  • File: packages/cli/src/acp-integration/acpAgent.ts:392-397 - The loadSettings(cwd) call in newSessionConfig reloads settings on every session creation. While this ensures correct scope for permission rules, consider adding a comment explaining why this reload is necessary vs. using cached settings.

  • File: packages/core/src/config/config.ts:2114-2176 - The registerCoreTool function is now async and all tool registrations are awaited sequentially. This is correct but could be parallelized with Promise.all() for faster initialization if tool registration doesn't have interdependencies.

  • File: packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx:77-92 - The reordering of onConfirm(outcome) before resolveDiffFromCli is a good fix for the race condition, but consider adding a comment explaining the specific race that was occurring (ProceedOnce vs ProceedAlways overwrite).

  • File: packages/core/src/permissions/permission-manager.ts:543-594 - The new hasMatchingAskRule method is well-documented, but consider extracting the shell operation extraction logic (lines 577-589) into a helper to reduce duplication with the main hasRelevantRules method.

✅ Highlights

  • Excellent permission flow consolidation: The L3→L4→L5 pattern is now the single source of truth across all clients, eliminating divergent permission behavior
  • Smart compound command handling: Filtering already-allowed sub-commands from confirmation prompts while still offering "Always Allow" for remaining sub-commands is a thoughtful UX improvement
  • Proper async PermissionManager: Making isCommandAllowed and evaluate async enables proper AST-based analysis for shell commands, improving accuracy of read-only detection
  • Comprehensive test updates: Test changes properly cover the new async behavior, compound command scenarios, and the distinction between explicit ask rules vs. default fallback
  • IDE diff suppression in auto modes: Correctly preventing IDE diffs from opening in AUTO_EDIT and YOLO modes maintains consistent behavior across CLI and IDE clients

PermissionManager.isToolEnabled was changed to async in this PR but the
call site in Session.runTool was not updated, causing the Promise to be
evaluated as a truthy value and the L1 tool-enablement check to always
pass — effectively disabling permission denial in the ACP session path.
Verify that when PermissionManager.isToolEnabled resolves to false the
tool is never executed and no permission dialog is opened.  This guards
against the async-await regression fixed in the previous commit.
When building diff content for edit-type permission requests, use
confirmation.filePath (full path) when available, falling back to
confirmation.fileName. This aligns with the test expectation and
ensures SubAgentTracker sends the correct file path in ACP permission
dialogs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants