Configuring GitHub Copilot Workspace for a TypeScript monorepo means writing a .github/copilot-instructions.md that states package boundaries explicitly, because Copilot's default context retrieval treats a Yarn or pnpm workspace as one undifferentiated codebase. The specific failure that prompted this setup: Copilot Workspace suggested a fix for a failing test in blogs/coding-dunia, and the suggested code referenced a component that only existed in blogs/real-smart-home, a completely different Astro project sharing the same monorepo. The two packages had similarly-named files, and without an explicit scoping hint, Copilot's retrieval pulled from the wrong one.
What Instructions File Fixed It?
<!-- .github/copilot-instructions.md -->
# Repository Structure
This is a Yarn 4 workspace monorepo. Packages live under two roots:
- `packages/*`: shared TypeScript libraries, consumed by blog sites via
`@local/blog-toolkit`. Changes here affect every blog.
- `blogs/*`: individual Astro sites, each with its own `package.json`,
`astro.config.mjs`, and `src/content/` collection. Each blog is
independent; code in one blog's `src/` never imports from another
blog's `src/` directly.
# When Suggesting a Fix
- Check which package the file under discussion belongs to before
suggesting a related file, changes to `blogs/coding-dunia/src/`
should never reference `blogs/real-smart-home/src/` or vice versa.
- Shared logic belongs in `packages/shared/src/`, not duplicated across
blog packages. If a fix looks like it should be shared, check
`packages/shared/src/` for an existing utility first.
# Conventions
- TypeScript strict mode is enabled everywhere; don't suggest `any`.
- Tests use Vitest, files are `*.test.ts` under `tests/`.
This is read automatically on every Copilot Workspace session in the repo, it's the equivalent of a project's CLAUDE.md for Claude Code, giving the same persistent context without needing to restate the monorepo's structure in every individual prompt. Writing it took about twenty minutes the first time, mostly spent deciding how much detail belonged at the root level versus a package-scoped file, and it has needed only two small edits since, both times after adding a new blog package to the monorepo.
Monorepo-aware context is the term for a tool that respects workspace boundaries when gathering surrounding code as prompt context, rather than treating a repo's full file tree as one flat search space. Per the GitHub Copilot docs, .github/copilot-instructions.md is read for every chat, inline suggestion, and Workspace session in the repository, so a single well-scoped file pays off across every future session rather than needing to be re-explained.
How Do You Scope a Session to One Package?
Beyond the persistent instructions file, Copilot Workspace sessions themselves benefit from an explicit scope statement at the start of a task description, rather than relying on file selection alone to communicate intent:
Task: Fix the failing test in blogs/coding-dunia/tests/unit/validator.test.ts
Scope: This task only touches blogs/coding-dunia/. Do not modify files
in packages/shared/ or any other blogs/* package, even if a similar
pattern exists there.
Being explicit about what's out of scope, not just what's in scope, cuts down on a specific failure mode: Copilot proposing a "consistency" fix that touches a sibling package's file because it found a similar pattern there, when the actual task only needed the one file changed.
- State the scoped directory first, before describing the actual bug or feature.
- List at least one specific sibling package the task should NOT touch, even if the instructions file already covers boundaries generally.
- If the task genuinely needs a shared-package change, say so explicitly rather than letting Copilot infer it from a pattern match.
Being explicit about what's out of scope, not just what's in scope, is what stops Copilot from touching a sibling package's file it shouldn't.
This costs one extra line per task description, and in about three months of daily use across a six-package monorepo, it cut cross-package suggestion bleed from a near-weekly annoyance to something that comes up once every few weeks, usually on a genuinely ambiguous task where the scope line itself was underspecified rather than missing. The persistent instructions file handles the general case; the per-task scope line handles the specific one, and neither substitutes fully for the other.
How Do You Handle Shared Package Version Drift?
Monorepos often have version drift between packages, a dependency pinned differently in blogs/coding-dunia/package.json than in blogs/real-smart-home/package.json, sometimes deliberately. Without guidance, Copilot's suggestions can assume a version's API that isn't actually what's installed in the specific package being edited:
# In .github/copilot-instructions.md
# Dependency Versions
Package versions can differ between `blogs/*` sites. Before suggesting
an API from a dependency (React, Astro, etc.), check that package's own
`package.json` for its actual installed version, don't assume the
version used in a different blogs/* package applies here.
This single paragraph fixed a recurring issue where a Copilot suggestion used a React 19-only API (use()) in a package still on React 18, because a sibling package in the same monorepo happened to be on React 19 and Copilot's retrieval picked up that context instead. The fix cost five lines in the instructions file and eliminated a class of bug that would otherwise have shipped and failed only at build time, once tsc hit an API that didn't exist in the installed React version.
Version drift is the general term for two packages in the same repository depending on different versions of the same library, whether deliberately (a slow migration in progress) or accidentally (a stale lockfile in one workspace). A monorepo with 6 blog packages sharing packages/shared/ but each pinning their own Astro and React versions independently is a textbook case, and it's exactly the situation where an AI tool without explicit per-package awareness produces suggestions that compile in one package and fail in another.
Without vs With copilot-instructions.md
| Without the instructions file | With the instructions file | |
|---|---|---|
| Package boundaries | Treated as one undifferentiated codebase | Explicit: blogs/* packages never cross-reference each other's src/ |
| Dependency versions | Assumes a sibling package's version applies | Checks the specific package's own package.json first |
| Repeat cost | Must be restated in every prompt | Read automatically on every session |
| Observed failure | Suggested a React 19-only API in a React 18 package | Fixed with one paragraph of guidance |
What Do Directory-Scoped Instructions Add?
For monorepos with genuinely divergent package conventions, not just different dependency versions, a nested .github/copilot-instructions.md inside a specific package directory can layer additional, package-specific guidance on top of the root file, where the platform supports it:
<!-- blogs/coding-dunia/.github/copilot-instructions.md -->
# Coding Dunia Specific Conventions
This blog uses Astro Content Collections with a strict `category` enum
(see src/content.config.ts). Never suggest a category value outside:
react, typescript, javascript, nodejs, css, performance, tooling.
The root file covers repo-wide structure; a package-scoped file covers the specific conventions that only make sense in the context of that one package, like a closed content-category enum that would be meaningless guidance to apply repo-wide. Not every platform supports directory-scoped instructions the same way, so check the current Copilot Workspace docs for the specific nesting behavior before assuming a package-level file layers on top rather than replacing the root one outright.
Two package-scoped files are usually the practical ceiling before the setup becomes harder to maintain than it's worth. Beyond that, according to the GitHub Copilot docs, the tool still merges root and nested instructions rather than picking one, so a genuinely large monorepo with a dozen divergent packages is better served by keeping most conventions at the root and reserving nested files for the one or two packages that are truly unusual.
Conclusion
The gap between "Copilot works fine on a small project" and "Copilot keeps suggesting the wrong package's file" is almost always a missing instructions file, not a limitation of the tool itself. A .github/copilot-instructions.md that states the monorepo's package boundaries, shared-vs-independent code locations, and per-package version differences fixes the cross-contamination failure mode directly, and it only needs to be written once per repo, not repeated in every prompt. Write it before the first cross-package suggestion bug shows up, not after.