Skip to content

task md:lint corrupts the .github/copilot-instructions.md symlink on Windows #315

Description

What happened

Running the documented task md:lint on Windows modifies .github/copilot-instructions.md, which is a symlink to ../AGENTS.md. Committing that change would replace the symlink with a broken target path and silently detach the repository's Copilot instructions from AGENTS.md.

Why it happens

.github/copilot-instructions.md is stored as a symlink (mode 120000) whose blob content is the literal string ../AGENTS.md with no trailing newline.

Git for Windows defaults to core.symlinks=false. Under that setting the entry is checked out as a plain text file containing the target path rather than a real symlink. markdownlint-cli2 then sees an ordinary .md file that does not end in a newline and "fixes" it:

.github/copilot-instructions.md:1:12 error MD047/single-trailing-newline Files should end with a single newline character

The resulting diff against a clean tree:

diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index be77ac8..6afa560 120000
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -1 +1 @@
-../AGENTS.md
\ No newline at end of file
+../AGENTS.md

The mode stays 120000, so the entry is still a symlink as far as Git is concerned, but its target becomes "../AGENTS.md\n" — a path that does not exist.

Reproduction

On Windows with core.symlinks=false (the default):

git clone https://github.com/Azure/mpf.git
cd mpf
task md:tools
task md:lint
git status --short   # M .github/copilot-instructions.md

Isolated to confirm which tool is responsible:

task md:run:markdownlint-cli2:fix --force
git status --short   # M .github/copilot-instructions.md

markdown-table-formatter does not touch the file. markdownlint-cli2 is the one that rewrites it.

Why it is easy to miss

The change is a single invisible trailing newline in a one line file. Anyone who runs the documented lint task and then stages with git add -A or git commit -a will pick it up without noticing, and the diff looks harmless in review. I hit this while fixing an unrelated markdown table in #314 and only caught it because the file was outside the scope of my change.

Suggested fix

Exclude the symlink from linting in .github/linters/.markdownlint-cli2.yaml:

ignores:
  - .git
  - "**/node_modules/**"
  - .copilot-tracking/**
  - venv/**
  - .venv/**
  - .github/copilot-instructions.md

That keeps the current symlink layout, which is nice because AGENTS.md stays the single source of truth.

Alternatives, if you would prefer not to special case a path:

  • Disable MD047 for that file via an inline configuration comment, though that is awkward since any content added to a symlink defeats its purpose.
  • Replace the symlink with a small stub file that points readers at AGENTS.md, avoiding symlink portability issues altogether at the cost of duplication.

The ignore entry seems like the smallest change.

Environment

  • Windows, Git for Windows with core.symlinks=false
  • markdownlint-cli2 v0.23.1 (markdownlint v0.41.1), as pinned by task md:install:markdownlint-cli2
  • main as of 2026-07-27

This is not specific to my checkout. Any Windows contributor with default Git settings who runs the documented lint task will reproduce it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions