DEV Community

Cover image for Pet peeves with GitHub Copilot Instructions and my solution
Ronald Rey
Ronald Rey

Posted on

Pet peeves with GitHub Copilot Instructions and my solution

This blog post was originally published on my own web site.

Photo by Łukasz Łada on Unsplash


One of my favorite features of Claude Code is .claude/rules/, docs here:

https://code.claude.com/docs/en/memory#organize-rules-with-claude/rules/.

If you're not yet aware, it allows you to create arbitrary markdown files with instructions or guidance for the model that will be loaded only when the files in your current context match the glob patterns in the paths of the top front-matter, for instance:

.claude/rules/api.md

---
paths:
  - 'src/api/**/*.ts'
---

# API Development Rules

- All API endpoints must include input validation
- Use the standard error response format
- Include OpenAPI documentation comments
Enter fullscreen mode Exit fullscreen mode

This feature is fantastic because it allows you to trim down a lot of the guidance that you'd probably put in CLAUDE.md that is not really applicable globally, saving you money on input tokens and improving the overall performance of your agent. I also prefer it over nested CLAUDE.md files because it keeps all the rules centralized in the same location making it easier for humans to find and parse through as well.

Rules are not standardized

Other tools might have an equivalent to this, for example, Cursor has Cursor rules.

Unfortunately though, if you go to Cursor's docs you'll notice that the rule format is slightly different, it requires a globs property instead of paths in the front-matter.

GitHub Copilot also has its own format. Instead of rules they call it "instructions" and they have to be located in .github/instructions/*.instructions.md and have an applyTo property in the front-matter.

For instance, paths is an array of strings, whereas applyTo, the equivalent in Copilot Instructions, is a single string that needs a comma-delitemed list of patterns. This becomes borderline unreadable when you need more than two patterns.

This is very annoying. Engineers have different preferences and I don't think it's out of the question for different colleagues on a single team to be using different tools even when working on the same codebase. This is probably not very common though, most likely teams probably agree on AI tooling choices for consistency and precisely because of this lack of standardization.

Artificial Intelligence technology is still very young so it seems like different vendors have not yet reached a point of convergence. Ideally, there's a standard format like .agent/rules that all major providers support regardless of the harness. This is on my wish list. Hopefully, we get there within a year, but I’m not holding my breath.

Going back to Copilot though, props to Microsoft as even though they have their own format, it also picks up .claude/rules as well by default, so that's a win for cross-compatibility. See their docs on Use custom instructions in VS Code
.

There's one major issue though...

The missing @import

One of the clients I work for operates in a very restricted security environment and doesn't provide or allow the use of Claude Code, they only have GitHub Copilot approved through the CLI, VSCode or IntelliJ plugins (for the parts of the organization that prefer to use IntelliJ).

As I was setting up one of our repos for AI-assisted development workflows I was glad to notice the cross-compatibility intent of Copilot to support Claude rules since I feel their format is superior.

So naturally I reached for the Claude Rules format instead... but it didn't take long for me to notice that it's missing an important feature I was hoping to use right away: @path/to/file syntax for loading referenced files into context at launch. Let me explain.

Looking back at the example from above, Claude Code can use the following syntax to inline a referenced file directly:

.claude/rules/api.md

---
paths:
  - 'src/api/**/*.ts'
---

@../../docs/api.md
Enter fullscreen mode Exit fullscreen mode

For the project I was working on today this feature comes really handy because we have a ton of pre-AI docs versioned controlled into the repo that are located on a different path. They should remain in that separate path because they're not LLM-specific rules but most importantly because they're integrated with Backstage for generating online wikis and the integration workflows expect a specific convention of disk location.

If you read the VSCode docs, it misleadingly says:

"To reference specific context in your instructions, such as files or URLs, you can use Markdown links."

However, in practice the linked references are not actually loaded or read by Copilot at all. It loads the instructions, it sees the references, but it doesn't execute a tool to see the contents of the files. The instructions loading process is very static and just includes the plain contents of the instructions itself.

Based on online research, this appears to be expected behavior. Those links are treated as secondary context and not loaded unless Copilot determines it needs them based on the ongoing conversational exchange. This matches my testing as well, sometimes I noticed they did load later on as I prompted more, but as you might guess, this is not deterministic, which makes me strongly question why the docs were phrased that way and don't clearly explain how it works.

This limitation was a deal breaker for me. The only solution was to inline the contents of every instruction I needed in the instructions file itself, and because of the setup I mentioned earlier this would've meant duplicating docs in two places, which is a no-go.

My workaround

I thought about it and decided to implement my own version of the @path/to/file syntax from Claude Code.

I wrote a simple Node.js script that parses through all instruction files looking for patterns like <!-- @import path/to/file --> and replacing them with the referenced contents. I opted to use HTML comments because they are ignored by LLMs.

So now, I author my instruction files like this:

.github/instructions/api.instructions.md

---
applyTo: 'src/api/**/*.ts'
---

<!-- @import ../../docs/api.md -->
Enter fullscreen mode Exit fullscreen mode

When the script runs, this gets expanded in-place to the following:

.github/instructions/api.instructions.md

---
applyTo: 'src/api/**/*.ts'
---

<!-- @import ../../docs/api.md -->
<!-- SYNCED-DOC:START source="../../docs/api.md" -->

# API Development Rules

- All API endpoints must include input validation
- Use the standard error response format
- Include OpenAPI documentation comments

<!-- SYNCED-DOC:END source="../../docs/api.md" -->
Enter fullscreen mode Exit fullscreen mode

The wrapping comments mark where my script should insert replacements or remove existing blocks when import directives change.

I added a git precommit hook that runs the script when instruction files are modified, and also a CI check to make sure the existing versioned-controlled instructions files matches the expected output in case a hook gets skipped or something else happens. If there are errors reading from the imported file the script exists with an error which would block CI.

Top comments (0)