Skip to content

Copilot app ships a bundled git with core.symlinks=false, silently breaking all symlinks in app-created worktrees on Windows #3280

Description

@aarmstrong128

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

  1. On Windows, use a repo containing tracked symlinks ( has 28).
  2. Create a worktree-backed session via the Copilot app.
  3. 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.mdAGENTS.md — the repo's top-level instruction file.

  • apps/storybook/public/fonts, apps/visual-compare/public/fontspackages/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

  1. 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).

  2. 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.

  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions