Skip to content

GitHub Copilot Best Practices: The Habits That Actually Stuck

GitHub Copilot Best Practices: The Habits That Actually Stuck

Confession: I’ve been using GitHub Copilot since the public beta and I’ve changed how I work with it about four times. The first two times were embarrassing. I’d accept whatever it suggested, mostly because typing felt slower than tabbing. By month three I had a small graveyard of subtle bugs that I’d shipped because the suggestion looked plausible and I didn’t read it carefully enough.

Two years later, the habits that survived are pretty different from where I started. This isn’t a tour of every feature. It’s the handful of github copilot best practices that I actually still do, plus a couple I’ve quietly stopped doing because they didn’t pay off.

If you want a broader take on how Copilot fits next to Cursor and Claude Code, I wrote that up in my cursor vs Copilot vs Claude Code piece. This post zooms in on Copilot itself: the workflow inside VS Code, in a real codebase, that I lean on every day.

I write the comment first, then the function

The biggest behaviour change for me was treating the comment above a function as the prompt. The official docs call this “prompt crafting” and it sounds obvious. It isn’t. For about a year I’d type a function signature and let Copilot guess the body. The completions were okay. They were rarely what I wanted on the first try.

Now I do this:

// Before: signature-first, hope for the best
export async function syncOrders(userId: string) {
  // copilot fills in something generic
}
// After: write the requirements as a comment, signature next
// Sync orders for a single user from the upstream Shopify API.
// - skip orders older than 30 days
// - upsert into our `orders` table by external_id
// - return a count of {created, updated, skipped}
// - any 429 from Shopify, retry with exponential backoff up to 5 times
export async function syncOrders(userId: string): Promise<SyncResult> {
  // copilot now has the constraints in scope
}

The second version gives Copilot the constraints it can’t infer from the signature: the 30-day filter, the upsert key, the retry policy. The completion isn’t always right, but it’s right often enough that I edit instead of rewrite. Cuts my “tab, undo, sigh” cycles roughly in half.

If you don’t already write docstring-style comments before every non-trivial function, this is the cheapest habit to pick up. You’re going to write the comment for code review anyway.

Custom instructions are the unlock most people miss

Of every Copilot feature I’ve tried, custom instructions is the one I see the fewest people using and the one that paid the most back. You drop a .github/copilot-instructions.md file into your repo and Copilot Chat injects its contents into the system prompt for every chat in that repo.

Mine is short. Eight or nine bullets. It looks roughly like this:

# copilot-instructions.md

- This is a TypeScript Next.js app on the App Router.
- We use Drizzle ORM, not Prisma. Don't suggest Prisma queries.
- Prefer server components by default. Only use 'use client' when we need interactivity.
- Database queries belong in `src/db/queries/`. Don't inline SQL in route handlers.
- Tests use Vitest, not Jest. The pattern is `*.test.ts` colocated with the file.
- Errors: throw a `DomainError` from `src/errors/`. Never throw plain `Error`.
- Logging: use the `log` import from `src/lib/log.ts`. No console.log.
- We don't use barrel files. Import from the source path directly.

The wins are quiet but constant. Copilot stops suggesting Prisma. It stops scattering console.log everywhere. It puts queries in the right folder. It uses our error class. None of those are hard to fix manually, but I was fixing them every single day before.

I check this file into the repo, not into my dotfiles, because the rules are repo-specific. If you have a monorepo, you can put one in each workspace.

Chat for design, inline for typing

Inline completions are great for the ten lines you already know how to write. Copilot Chat is for the part you’re stuck on. I learned to keep them in separate lanes.

When I open Chat, I’m usually doing one of three things. First, asking it to sketch a structure I haven’t fully thought through (“what would the API look like for X”), then keeping the shape and ignoring the rest. Second, pasting an error and a stack trace and asking what’s likely causing it. Worth saying: I read the answer, then go check the actual code, because Chat will confidently explain a bug that doesn’t exist. Third, asking it to write tests for a function I’ve already finished. That’s the single highest-value Chat use I’ve found.

What I don’t use Chat for: writing whole features. Every time I’ve tried, the result was structurally fine and tactically wrong in three places. Faster to write it myself with inline completions than to debug a Chat-generated module.

The official VS Code Copilot Chat docs cover the slash commands. The two I actually use are /tests and /explain. Everything else I’ve tried and dropped.

I read every diff. Yes, every one.

This is the boring one and it’s the one that catches the bugs.

The temptation with Copilot is to tab-accept and keep typing. You get into a rhythm, the suggestion looks right, you move on. A week later you’re debugging a function that’s quietly off by one because the AI swapped <= for < in a loop and you didn’t notice.

My rule: if Copilot wrote more than three lines, I read all of them before I move on. If it wrote a whole function, I read it twice. I’d rather lose ten seconds than ship a subtle bug that takes ninety minutes to track down later.

The other thing I do: I keep git diff open in a side terminal while I’m working. Not because I don’t trust the editor, but because seeing the diff in plain text strips away the syntax highlighting and the soft “Copilot agreed with me” feeling. Bugs hide in the colours.

When I turn it off

There are real cases where Copilot makes me worse. I turn it off when:

  • I’m writing a regex. Copilot’s regex completions look authoritative and are subtly wrong about half the time. I’d rather write it myself and test it.
  • I’m doing crypto. I want to think about every line, not autocomplete it. Same energy as auth flows.
  • I’m in a domain Copilot has no idea about, like internal DSLs or custom protocols, where the training data is a poor guide. The completions become noise.
  • I’m pairing with someone. Two cursors, two streams of suggestions, and one of you half-reading both. Just turn it off and have the conversation.

There’s a setting (github.copilot.enable) you can scope per-language in your settings.json. I have it disabled for markdown because I don’t want it finishing my sentences while I’m trying to think.

A specific note on AI-generated tests

I had to learn this the painful way: Copilot will happily generate tests that pass against the wrong behaviour. It reads your function, sees what it does, and writes assertions that match. That’s a regression detector, not a correctness check.

The fix is to write the test first, see it fail, then implement. Or, if the function exists, write the assertion yourself based on the spec and let Copilot fill in the setup boilerplate. Don’t let it author the assertions. That’s the part you have to think about.

What I’d actually start with on Monday

If you’re picking Copilot up this week, here’s the order I’d do it in.

Monday: write a short .github/copilot-instructions.md for one repo. Stack, conventions, things to avoid. Eight to twelve bullets. Don’t overthink it.

Tuesday: change one habit. Write a docstring-style comment above each non-trivial function before you start typing the body. That single change does most of the work.

Wednesday: try /tests on three small functions. See how often the tests are wrong against the spec. Calibrate your trust accordingly.

Friday: look back at the week’s commits. Notice how many had Copilot-generated lines you ended up rewriting. That number tells you where to invest next.

I cover this kind of workflow tuning across my work if you want a broader picture of how I think about AI-assisted development.

The short version: Copilot is a power tool, not autocomplete. The people who slow down enough to feed it real prompts and actually read its output get most of the value out of it. The rest of us ship the bugs and learn the hard way. I still get this wrong on tired Friday afternoons. Pick one habit from above, try it for a week, see if it sticks.