Short summary
Copilot breaks repo symlinks
Affected version or release
1.1.14
Installation context
No response
What happened?
Title
Environment
- GitHub Copilot app 1.1.14 / GitHub Copilot CLI 1.0.80
- Windows
- Bundled git:
github-copilot-git-2.53.0-4
- Repo:
msasg/Interstellar/epichan (Azure DevOps), worktree-backed session
Summary
The Copilot app ships its own git build and places it first on PATH. That git's system gitconfig hardcodes core.symlinks = false:
Plain text
C:\Users\<user>\AppData\Local\github-copilot-git-2.53.0-4\etc\gitconfig
[core]
symlinks = false
autocrlf = true
fscache = true
Every worktree the app creates is therefore checked out with symlink support disabled. Each tracked symlink (git mode 120000) is written to disk as a plain text file containing its target path rather than an actual link. In the affected repo this silently broke all 28 tracked symlinks.
This is not a machine privilege limitation. Unprivileged symlink creation is permitted on this host — verified with New-Item -ItemType SymbolicLink, which succeeds. The bundled git simply has the flag off, which is the Git-for-Windows installer default when "Enable symbolic links" is left unchecked. The app shipped that default.
Repro
- On Windows, use a repo containing tracked symlinks ( has 28).
- Create a worktree-backed session via the Copilot app.
- In the session:
Get-Item .claude | Select-Object LinkType
Expected: SymbolicLink
Actual: empty LinkType; .claude is a regular file whose contents are the literal string .copilot
Impact
1. All project skills silently vanish. The CLI discovers project skills from .github/skills/, .agents/skills/, or .claude/skills/. uses the widely-used pattern of symlinking .claude → .copilot so one tree serves both Claude-format and Copilot-format tooling. With the symlink unmaterialized, .claude/skills/ does not exist and all 36 project skills disappear.
The failure mode is bad because it is silent and misleading:
-
copilot skill list in the worktree prints only the 2 builtin skills, no warning.
-
skill({skill: "file-bug"}) returns Skill not found: file-bug.
-
The agent then improvises instead of running the codified workflow. In my case the requested skill was <skill name>, which is the only place the correct target Azure DevOps org is documented — improvising would have filed into the wrong org.
-
.copilot/rules/ still loads (they arrive as custom instructions via AGENTS.md), so the session looks correctly configured. This actively masks the problem.
2. The damage is far broader than skills. After enabling core.symlinks, git reported 27 additional T (typechange) entries that had been invisible:
-
All 23 .github/instructions/*.instructions.md → .copilot/rules/*.md — the VS Code / Copilot Chat rule surface, entirely broken.
-
.github/copilot-instructions.md → AGENTS.md — the repo's top-level instruction file.
-
apps/storybook/public/fonts, apps/visual-compare/public/fonts → packages/design-system/fonts — build/runtime font assets missing in three apps.
3. It hides itself from git status. With core.symlinks=false, git treats the plain file's contents as the symlink target, so status reads clean and nothing signals breakage. The 27 typechanges only surfaced after flipping the setting.
4. It is not repo-fixable. core.symlinks lives in per-install/per-clone config. It cannot be committed, and there is no .gitattributes equivalent. No repo can defend itself against this.
5. It is not repo-specific. Any repo with symlinks, opened in an app worktree on Windows, is affected.
Evidence that the app owns this
The same repo, same CLI, same machine, behaves correctly when checked out by the user's own Git for Windows:
| Checkout |
Created by |
.claude |
copilot skill list |
Q:\\src\\<repo name> (main) |
user's Git for Windows |
SymbolicLink → .copilot |
36 project skills |
| app worktree |
app's bundled git |
plain file |
0 project skills |
Config origin trace confirms the source is the app's git, not the repo:
Plain text
file:C:/Users/<user>/AppData/Local/github-copilot-git-2.53.0-4/etc/gitconfig core.symlinks=false
Neither the repo's local config nor the user's global config set it.
Verification of the fix
Creating a worktree with the flag enabled produces a correct checkout:
Plain text
git -C <repo> -c core.symlinks=true worktree add --detach <path> HEAD
→ .claude SymbolicLink .copilot .claude\skills resolves: True
A global override (git config --global core.symlinks true) also wins over the app's system config and fixes new app-created worktrees with no inline flag. After restoring with git checkout -- ., all 28 symlinks materialized, working tree clean, and skill discovery returned 38 skills.
Suggested fix
-
Primary: stop shipping core.symlinks = false in the bundled git's system gitconfig — or set core.symlinks=true explicitly when creating worktrees on hosts where symlink creation is permitted (detectable with a cheap probe).
-
Fallback for genuinely unprivileged hosts: after git worktree add, detect index entries with mode 120000 that materialized as regular files and warn loudly, rather than proceeding silently.
-
Defense in depth (CLI): consider having the CLI also scan .copilot/skills/ directly. Given the config directory is literally named .copilot, requiring a .claude symlink to reach it is a sharp edge — and it is the single point of failure that turned a git config default into total skill loss.
Related observation
The same bundled config sets core.autocrlf = true. That is a separate risk for repos containing shell scripts or fixtures with content-hash assertions, and may warrant its own review.
Steps to reproduce
No response
Expected behavior
No response
Additional context
No response
Short summary
Copilot breaks repo symlinks
Affected version or release
1.1.14
Installation context
No response
What happened?
Title
Environment
github-copilot-git-2.53.0-4msasg/Interstellar/epichan(Azure DevOps), worktree-backed sessionSummary
The Copilot app ships its own git build and places it first on PATH. That git's system gitconfig hardcodes
core.symlinks = false:Plain text
Every worktree the app creates is therefore checked out with symlink support disabled. Each tracked symlink (git mode
120000) is written to disk as a plain text file containing its target path rather than an actual link. In the affected repo this silently broke all 28 tracked symlinks.This is not a machine privilege limitation. Unprivileged symlink creation is permitted on this host — verified with
New-Item -ItemType SymbolicLink, which succeeds. The bundled git simply has the flag off, which is the Git-for-Windows installer default when "Enable symbolic links" is left unchecked. The app shipped that default.Repro
Get-Item .claude | Select-Object LinkTypeExpected:
SymbolicLinkActual: empty
LinkType;.claudeis a regular file whose contents are the literal string.copilotImpact
1. All project skills silently vanish. The CLI discovers project skills from
.github/skills/,.agents/skills/, or.claude/skills/. uses the widely-used pattern of symlinking.claude→.copilotso one tree serves both Claude-format and Copilot-format tooling. With the symlink unmaterialized,.claude/skills/does not exist and all 36 project skills disappear.The failure mode is bad because it is silent and misleading:
copilot skill listin the worktree prints only the 2 builtin skills, no warning.skill({skill: "file-bug"})returnsSkill not found: file-bug.The agent then improvises instead of running the codified workflow. In my case the requested skill was
<skill name>, which is the only place the correct target Azure DevOps org is documented — improvising would have filed into the wrong org..copilot/rules/still loads (they arrive as custom instructions viaAGENTS.md), so the session looks correctly configured. This actively masks the problem.2. The damage is far broader than skills. After enabling
core.symlinks, git reported 27 additionalT(typechange) entries that had been invisible:All 23
.github/instructions/*.instructions.md→.copilot/rules/*.md— the VS Code / Copilot Chat rule surface, entirely broken..github/copilot-instructions.md→AGENTS.md— the repo's top-level instruction file.apps/storybook/public/fonts,apps/visual-compare/public/fonts→packages/design-system/fonts— build/runtime font assets missing in three apps.3. It hides itself from
git status. Withcore.symlinks=false, git treats the plain file's contents as the symlink target, so status reads clean and nothing signals breakage. The 27 typechanges only surfaced after flipping the setting.4. It is not repo-fixable.
core.symlinkslives in per-install/per-clone config. It cannot be committed, and there is no.gitattributesequivalent. No repo can defend itself against this.5. It is not repo-specific. Any repo with symlinks, opened in an app worktree on Windows, is affected.
Evidence that the app owns this
The same repo, same CLI, same machine, behaves correctly when checked out by the user's own Git for Windows:
.claudecopilot skill listQ:\\src\\<repo name>(main)SymbolicLink→.copilotConfig origin trace confirms the source is the app's git, not the repo:
Plain text
Neither the repo's local config nor the user's global config set it.
Verification of the fix
Creating a worktree with the flag enabled produces a correct checkout:
Plain text
A global override (
git config --global core.symlinks true) also wins over the app's system config and fixes new app-created worktrees with no inline flag. After restoring withgit checkout -- ., all 28 symlinks materialized, working tree clean, and skill discovery returned 38 skills.Suggested fix
Primary: stop shipping
core.symlinks = falsein the bundled git's system gitconfig — or setcore.symlinks=trueexplicitly when creating worktrees on hosts where symlink creation is permitted (detectable with a cheap probe).Fallback for genuinely unprivileged hosts: after
git worktree add, detect index entries with mode120000that materialized as regular files and warn loudly, rather than proceeding silently.Defense in depth (CLI): consider having the CLI also scan
.copilot/skills/directly. Given the config directory is literally named.copilot, requiring a.claudesymlink to reach it is a sharp edge — and it is the single point of failure that turned a git config default into total skill loss.Related observation
The same bundled config sets
core.autocrlf = true. That is a separate risk for repos containing shell scripts or fixtures with content-hash assertions, and may warrant its own review.Steps to reproduce
No response
Expected behavior
No response
Additional context
No response