github / github/copilot-cli

Persistent permissions broken in git worktrees

Open
#1,644 0 comments 0 reactions 0 assignees View on GitHub
area:permissions
Dominant language
Shell
Stars
11.2k
Forks
1.9k
Avg merge
14h 16m
Merged PRs (30d)
6

Description

### 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

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.