github / github/copilot-cli

Persistent permissions broken in git worktrees

未关闭
#1,644 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看
area:permissions
主要语言
Shell
星标
11.2k
派生
1.9k
平均合并
14 小时 16 分钟
30 天内合并 PR
6

描述

### Describe the bug

Permission prompts in git worktrees show the main repo path instead of the worktree path. Approving permissions enters an infinite loop — each new session or sub-agent re-prompts because the stored approval key doesn't match the actual working directory.

### Affected version

0.0.410

### Steps to reproduce the behavior

1. Set `"experimental": true` in `~/.copilot/config.json`
2. Create a worktree: `git worktree add ../my-feature feature-branch`
3. Start Copilot CLI in the worktree
4. Trigger a permission prompt (edit, bash, etc.)
5. Prompt says "in this repo (`my-repo`)" instead of `my-feature`
6. Approve → reprompted again even in same session

### Expected behavior

No need to approve again what was marked as safe to do in current directory

### Additional context

Copilot investigation:
# Bug: PERSISTED_PERMISSIONS breaks git worktree support — location key resolves to wrong path

## Description

The `PERSISTED_PERMISSIONS` feature (introduced in v0.0.407) breaks permission handling in **git worktrees**. When enabled (via `"experimental": true` in config), the permission dialog only offers "approve permanently for this location" — but the location key is resolved via `git rev-parse --git-common-dir`, which returns the **main repository path** instead of the worktree path.

This causes:
1. Permission prompts display the wrong directory
2. Approvals are stored under the wrong path in `permissions-config.json`
3. Subsequent sessions in the same worktree **cannot find the stored approvals** (they look up using the main repo key, but the file paths they're checking are under the worktree)
4. Sub-agents (task tool) that start fresh permission contexts re-prompt for already-approved permissions

**Before v0.0.407**, permissions were session-scoped only and worked correctly in worktrees because they didn't rely on filesystem path matching.

## Environment

- **Copilot CLI**: 0.0.410
- **OS**: Ubuntu 20.04 (Linux 6.0.12)
- **Node.js**: v18.20.4
- **Config**: `"experimental": true` (enables `PERSISTED_PERMISSIONS` feature flag)
- **Git**: worktree setup with multiple parallel workspaces

## Steps to Reproduce

1. Ensure `"experimental": true` in `~/.copilot/config.json`
2. Have a git repository cloned at `/home/user/my-repo`
3. Create a worktree:
```bash
git worktree add /home/user/my-repo-feature feature-branch
```
4. `cd /home/user/my-repo-feature`
5. Start a Copilot CLI session: `copilot`
6. Trigger any action that requires permission (e.g., edit a file, run bash)
7. Notice the prompt says "in this repo (`my-repo`)" — wrong path
8. Approve the permission
9. Start a new session or trigger a sub-agent → **re-prompted for the same permission**

## Expected Behavior

- Permission prompt shows the worktree path: "in this repo (`my-repo-feature`)"
- Approval is stored under the worktree path
- New sessions and sub-agents in the same worktree find the stored approval

## Actual Behavior

- Prompt shows main repo: "in this repo (`my-repo`)"
- Approval stored under `/home/user/my-repo` in `permissions-config.json`
- New sessions/sub-agents resolve to the same wrong key but file paths don't match → re-prompt

## Regression Timeline

| Version | Date | Behavior |
|---------|------|----------|
| ≤ 0.0.406 | ≤ Feb 10 | Session-scoped permissions only → **worktrees worked fine** |
| **0.0.407** | Feb 12 | `PERSISTED_PERMISSIONS` added (staff-or-experimental) |
| 0.0.410 | Feb 16 | First `location-based permission` log entries; **worktrees broken** |

The `PERSISTED_PERMISSIONS` feature flag gates `"staff-or-experimental"`, so this affects users with `"experimental": true` or staff accounts.

## Root Cause: Source-Level Trace

The location key resolver function (`qme` in minified `index.js`):

```javascript
async function qme(cwd) {
// Step 1: Get worktree-aware path (CORRECT)
let toplevel = exec("git", ["rev-parse", "--show-toplevel"], {cwd}).stdout.trim();

// Step 2: Get common git dir (points to MAIN repo for worktrees)
let commonDir = exec("git", ["rev-parse", "--git-common-dir"], {cwd}).stdout.trim();

// Step 3: BUG — prefers dirname(commonDir) over toplevel
return isAbsolute(commonDir)
? { locationKey: dirname(commonDir) } // ← WRONG for worktrees
: { locationKey: toplevel }; // ← correct, but only used as fallback
}
```

For a normal repo, `--git-common-dir` returns `.git` (relative) → falls through to `toplevel` → **works**.
For a worktree, `--git-common-dir` returns `/home/user/my-repo/.git` (absolute) → uses `dirname()` → **returns main repo path**.

### Why the dialog doesn't offer session-scoped approval as fallback

When `PERSISTED_PERMISSIONS` is enabled, the permission dialog **replaces** the session-scoped option:

```javascript
if (featureFlags?.PERSISTED_PERMISSIONS && locationKey) {
// ONLY option: "Yes, and don't ask again for ... in this repo (my-repo)"
choices.push({kind: "approve-for-location", ...});
} else {
// Fallback: "Yes, and approve ... for the rest of the session"
choices.push({kind: "approve-for-session", ...});
}
```

So users with `experimental: true` **cannot** fall back to session-scoped permissions.

## Suggested Fix

Replace `dirname(commonDir)` with `toplevel` in the location key resolver:

```javascript
// Before (buggy for worktrees):
return isAbsolute(commonDir)
? { locationKey: normalize(dirname(commonDir)), locationType: "repo" }
: { locationKey: normalize(toplevel), locationType: "repo" };

// After (worktree-aware):
return { locationKey: normalize(toplevel), locationType: "repo" };
```

`--show-toplevel` is already computed and is worktree-aware. For normal repos it returns the same value as `dirname(.git)`. For worktrees it returns the correct worktree root.

## Workaround

Manually duplicate the permission entry in `~/.copilot/permissions-config.json` for each worktree path:
```json
{
"locations": {
"/home/user/my-repo": { "tool_approvals": [...] },
"/home/user/my-repo-feature": { "tool_approvals": [...] }
}
}
```

Or disable the feature flag by setting `"experimental": false` in `~/.copilot/config.json` (but this disables other experimental features too).

## Impact

- **Breaks git worktree workflows** for all users with `experimental: true` or staff accounts
- **No session-scoped fallback** — the feature replaces rather than supplements the old approval flow
- **Sub-agents re-prompt** — agents spawned via task tool get fresh permission contexts and can't find stored approvals
- **Blocks autonomous workflows** — parallel development with multiple worktrees requires manual config editing per worktree

贡献指南

打开贡献指南

评估

这个 Issue 还没有评估数据。

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。