Sharing Instructions with the Team

by John Miller | August 30, 2025

When working with GitHub Copilot across multiple projects, you'll quickly face a common challenge: how do you share generic instructions (ie. coding standards and best practices) with team members working on different projects without duplicating instruction files in every repository? This post walks you through prompting Copilot to implement a solution leveraging symlinks and junction points.

What Are Instruction Files?

For those of you new to using GitHub Copilot, instruction files are the blueprint for customizing how Copilot agents behave, respond, and interact with tools. Instruction files include natural language guidance that shapes the agent's tone, reasoning style, and response formatting, making them essential for tailoring Copilot to specific workflows, domains, or compliance requirements. Instruction files give you granular control over how the agent interprets user intent and executes tasks.

Instruction files for GitHub Copilot agents can be located within the project repository in the .github folder or a subfolder of .github. They can also reside in the user's home directory in a .github prompts folder. The project repository location takes precedence over the user home directory location.

Instruction files in the project folder are in scope for that project. Instruction files in the user home directory are available to all projects. Instruction files in the user home directory are limited in that the can only reside in the prompts folder.

For instructions that are not project specific, such as coding standards and practices, it is desirable to share them across multiple projects. However, duplicating these files in each project repository is inefficient and error-prone. Instead, we can use symbolic links (symlinks) on Linux and macOS, and junction points on Windows, to create a single source of truth for shared instructions.

Ideally it would be nice to create a link in the user folder once and have it be used across all projects. Due to the limitations on the user home directory location, creating a link there would require that all of the shared instructions be in a single folder in the source and it would preclude storing any personal instructions.

Therefore, we need to establish these links within each project. Our solution involves creating these links seamlessly.

A symlink (symbolic link) is a special type of file that points to another file or directory, allowing you to access the target as if it were located at the link's location. Symlinks are commonly used on Linux and macOS to create shortcuts or shared folders. A junction point is a similar concept used on Windows; it creates a link to a directory, making the contents of the target directory appear in the linked location. Both mechanisms enable you to share or reuse files and folders across different locations without duplicating data, which simplifies maintenance and updates.

The Prompt

The solution involves creating a repo that contains the instruction files to be shared. Developers on the team can clone the repo locally and pull changes as needed. They can also change or add instructions to the repo and push those changes to the origin. Making them available to other team members.

Once the repo is established, we need to provide guidance for the team in creating the links to the shared instructions. We'll ask Copilot to create scripts for each OS (Windows, Linux, macOS) that will create the appropriate link from the project repository to the shared instructions folder in the central repo. The scripts will also ensure that the linked folder is added to the project's .gitignore file to prevent accidental commits of the shared instructions to the project repos. In addition we'll ask Copilot to create a README file with instructions on how to use the scripts.

Here is the prompt I gave GitHub Copilot:

Write three scripts to create a symlink or junction point for windows, Linux, and Mac.

Use a symlink for Linux and Mac, and a junction point for Windows using 
the New-Item -ItemType Junction cmdlet.

The symlink or junction point goes to the `./instructions` folder in this GitHubCopilot repo 
from the `./github/instructions/shared` folder in the Project repo.

You'll need the paths to both the GitHubCopilot repo and the Project repo in order to proceed.

Ensure that the Project repo's `./github/instructions/shared` folder is in the Project's `.gitignore` file. 
Create the file if it doesn't exist. We don't want the shared instructions to be pushed into the project repo.

Create a `Readme.md` the describes the purpose and use of the scripts.

Put all the files in a `shared-instructions` folder in the GitHubCopilot repo.

This prompt instructs Copilot (or ChatGPT 5) to automate the process of sharing a set of instruction files across multiple projects by generating platform-specific scripts. The scripts are designed to create either a symbolic link (symlink) or a junction point from a project repository to a central instructions folder in the GitHubCopilot repo. For Linux and macOS, the scripts use symlinks, while on Windows, they use NTFS junctions via PowerShell's New-Item -ItemType Junction. The prompt also requires the scripts to ensure that the shared instructions folder is listed in the project's .gitignore file, preventing accidental commits of shared files.

Copilot and ChatGPT 5 interpret this prompt as a request for practical automation tools that streamline collaboration and maintain consistency across development environments. They recognize the need for cross-platform compatibility, idempotency, and version control hygiene. The prompt's instructions lead them to generate three scripts (one for each OS) and a README file, all organized in a shared-instructions folder. The resulting solution enables teams to easily link to and update shared instruction files, reducing duplication and simplifying maintenance.

Here is the README.md file generated from the prompt:

# Shared Instructions Symlink/Junction Scripts

These scripts create a platform-appropriate link from a Project repository to the shared `instructions` folder 
that lives in the `GitHubCopilot` repo. This lets multiple projects reuse a single, centrally maintained set 
of instruction files without copying them.

Link layout created:

<ProjectRepo>/github/instructions/shared  ->  <GitHubCopilotRepo>/instructions

Platform behavior:

Platform Mechanism Script
Windows NTFS Junction create-shared-link-windows.ps1
Linux Symbolic link create-shared-link-linux.sh
macOS Symbolic link create-shared-link-mac.sh

The destination path inside the Project repo (github/instructions/shared/) is automatically added (or appended)
to the Project repo's .gitignore so linked files are never committed to the Project repository.

Why a Junction on Windows?

Junctions avoid some permission / policy issues that can affect symlinks on Windows machines without Developer Mode enabled. They behave like directory symlinks for our purpose.

Prerequisites

  • You have both the Project repo and the GitHubCopilot repo cloned locally.
  • PowerShell 5+ (Windows script) or bash + Python 3 (for relative path computation) on Linux/macOS. (If Python 3 is not available, omit --relative.)

Parameters

All scripts require the root path of both repositories.

Parameter Windows Linux/macOS Description
GitHubCopilotRepo / –copilot Required Required Path to the GitHubCopilot repo root (where ./instructions exists).
ProjectRepo / –project Required Required Path to the Project repo root (where ./github/instructions/shared will be created).
-Force / –force Optional Optional Replace existing destination (if mismatch or not link).
–relative n/a Optional Create a relative symlink instead of an absolute one.

Usage Examples

Windows (PowerShell)

cd C:\git\GitHubCopilot\shared-instructions
./create-shared-link-windows.ps1 -GitHubCopilotRepo C:\git\GitHubCopilot -ProjectRepo C:\git\MyProject

# Force recreation
./create-shared-link-windows.ps1 -GitHubCopilotRepo ..\GitHubCopilot -ProjectRepo C:\git\MyProject -Force

Linux

cd ~/git/GitHubCopilot/shared-instructions
./create-shared-link-linux.sh --copilot ~/git/GitHubCopilot --project ~/git/MyProject

# Relative link, force recreate
./create-shared-link-linux.sh --copilot ~/git/GitHubCopilot --project ~/git/MyProject --relative --force

macOS

cd ~/git/GitHubCopilot/shared-instructions
./create-shared-link-mac.sh --copilot ~/git/GitHubCopilot --project ~/git/MyProject

Idempotency

Running a script again will leave an already-correct link untouched (unless --force / -Force is specified) and will still ensure the .gitignore entry exists.

.gitignore Handling

The following entry is ensured inside the Project repo .gitignore (file created if missing):

github/instructions/shared/

Troubleshooting

  • Destination exists and is not a link: Re-run with -Force / --force.
  • Permission errors on Windows: Ensure PowerShell is running with adequate permissions.
  • Symlink creation denied on Windows: Junctions should bypass typical symlink permission issues; if problems persist, check antivirus or corporate policy restrictions.

Removal

Simply delete the destination path inside the Project repo:

Remove-Item <ProjectRepo>\github\instructions\shared -Recurse -Force

or

rm -rf <ProjectRepo>/github/instructions/shared

Maintenance

If new shared instruction files are added to GitHubCopilot/instructions, they become available to all linked projects immediately—no changes needed in individual projects.

A small clarification on the maintenance. New shared instruction files added to GitHubCopilot/instructions will not automatically be available in all linked projects without any additional steps. They become available after a pull from the repo.

Here is the Linux script generated from the prompt:

#!/usr/bin/env bash
set -euo pipefail

# Creates (or refreshes) a symbolic link in the Project repo pointing to the
# ./instructions folder in the GitHubCopilot repo.
#
# Destination (link created): <ProjectRepo>/github/instructions/shared
# Source (link target):       <GitHubCopilotRepo>/instructions
#
# Arguments:
#   --copilot <path>   Path to GitHubCopilot repo root (where ./instructions exists)
#   --project <path>   Path to Project repo root (where ./github/instructions/shared will be created)
#   --force            Remove and recreate existing destination if present
#   --relative         Create a relative symlink (default: absolute)
#
# Example:
#   ./create-shared-link-linux.sh --copilot ../GitHubCopilot --project . --relative

set +u
FORCE=0
RELATIVE=0
COPILOT=""
PROJECT=""
set -u

while [[ $# -gt 0 ]]; do
  case "$1" in
    --copilot) COPILOT="$2"; shift 2;;
    --project) PROJECT="$2"; shift 2;;
    --force) FORCE=1; shift;;
    --relative) RELATIVE=1; shift;;
    -h|--help)
      grep '^#' "$0" | sed 's/^# //'
      exit 0
      ;;
    *) echo "Unknown arg: $1" >&2; exit 1;;
  esac
done

if [[ -z "$COPILOT" || -z "$PROJECT" ]]; then
  echo "--copilot and --project are required" >&2
  exit 1
fi

COPILOT=$(cd "$COPILOT" && pwd -P)
PROJECT=$(cd "$PROJECT" && pwd -P)

SOURCE="$COPILOT/instructions"
DEST_PARENT="$PROJECT/github/instructions"
DEST="$DEST_PARENT/shared"

if [[ ! -d "$SOURCE" ]]; then
  echo "Source instructions folder not found: $SOURCE" >&2
  exit 1
fi

mkdir -p "$DEST_PARENT"

if [[ -e "$DEST" && ! -L "$DEST" ]]; then
  if [[ $FORCE -eq 1 ]]; then
    rm -rf "$DEST"
  else
    echo "Destination exists and is not a symlink: $DEST (use --force)" >&2
    exit 1
  fi
fi

if [[ -L "$DEST" ]]; then
  TARGET=$(readlink "$DEST")
  ABS_TARGET=$(cd "$(dirname "$DEST")" && cd "$TARGET" 2>/dev/null && pwd -P || true)
  if [[ "$ABS_TARGET" == "$SOURCE" && $FORCE -ne 1 ]]; then
    echo "Symlink already correct. Nothing to do."
    exit 0
  else
    rm "$DEST"
  fi
fi

LINK_TARGET="$SOURCE"
if [[ $RELATIVE -eq 1 ]]; then
  # compute relative path from DEST_PARENT to SOURCE
  LINK_TARGET=$(python3 - "$DEST_PARENT" "$SOURCE" <<'PY'
import os,sys
base=sys.argv[1]
target=sys.argv[2]
print(os.path.relpath(target, start=base))
PY
  )
fi

ln -s "$LINK_TARGET" "$DEST"
echo "Created symlink: $DEST -> $LINK_TARGET"

# Ensure .gitignore entry
GI="$PROJECT/.gitignore"
ENTRY='github/instructions/shared/'
if [[ -f "$GI" ]]; then
  if ! grep -Fxq "$ENTRY" "$GI"; then
    printf "\n%s\n" "$ENTRY" >> "$GI"
    echo "Appended to .gitignore: $ENTRY"
  fi
else
  printf '# Added automatically to ignore shared instructions\n%s\n' "$ENTRY" > "$GI"
  echo "Created .gitignore with entry: $ENTRY"
fi

echo "Done."

This Bash script automates the creation or refreshing of a symbolic link in a project repository, pointing to an instructions folder in a separate GitHub Copilot repository. Its main purpose is to ensure that the project always has access to shared instructions, without duplicating files.

The script accepts several command-line arguments: –copilot specifies the path to the GitHub Copilot repo, –project specifies the path to the project repo, –force allows overwriting an existing destination, and –relative creates a relative symlink instead of an absolute one. It validates that both source and destination paths are provided and resolves them to absolute paths for reliability.

Before creating the symlink, the script checks if the source directory exists. It then prepares the destination parent directory. If the destination already exists and is not a symlink, it will only remove it if the –force flag is set. If the destination is a symlink but points to the wrong target, it will be replaced. The script can compute a relative path for the symlink using a short Python snippet if requested.

After creating the symlink, the script ensures that the new link is ignored by Git by adding an entry to the project's .gitignore file. If the file does not exist, it creates one with the appropriate entry. This prevents accidental commits of the shared instructions link. The script provides clear output messages for each major step, helping users understand what actions were taken.

Here is the macOS script generated from the prompt:

#!/usr/bin/env bash
# macOS version (identical logic to linux script; kept separate for clarity & customization).
set -euo pipefail

# See linux script for detailed commentary.

set +u
FORCE=0
RELATIVE=0
COPILOT=""
PROJECT=""
set -u

while [[ $# -gt 0 ]]; do
  case "$1" in
    --copilot) COPILOT="$2"; shift 2;;
    --project) PROJECT="$2"; shift 2;;
    --force) FORCE=1; shift;;
    --relative) RELATIVE=1; shift;;
    -h|--help)
      grep '^#' "$0" | sed 's/^# //'
      exit 0
      ;;
    *) echo "Unknown arg: $1" >&2; exit 1;;
  esac
done

if [[ -z "$COPILOT" || -z "$PROJECT" ]]; then
  echo "--copilot and --project are required" >&2
  exit 1
fi

COPILOT=$(cd "$COPILOT" && pwd -P)
PROJECT=$(cd "$PROJECT" && pwd -P)
SOURCE="$COPILOT/instructions"
DEST_PARENT="$PROJECT/github/instructions"
DEST="$DEST_PARENT/shared"

[[ -d "$SOURCE" ]] || { echo "Source instructions folder not found: $SOURCE" >&2; exit 1; }
mkdir -p "$DEST_PARENT"

if [[ -e "$DEST" && ! -L "$DEST" ]]; then
  if [[ $FORCE -eq 1 ]]; then rm -rf "$DEST"; else echo "Destination exists and is not a symlink (use --force): $DEST" >&2; exit 1; fi
fi

if [[ -L "$DEST" ]]; then
  TARGET=$(readlink "$DEST")
  ABS_TARGET=$(cd "$(dirname "$DEST")" && cd "$TARGET" 2>/dev/null && pwd -P || true)
  if [[ "$ABS_TARGET" == "$SOURCE" && $FORCE -ne 1 ]]; then
    echo "Symlink already correct. Nothing to do."; exit 0
  else
    rm "$DEST"
  fi
fi

LINK_TARGET="$SOURCE"
if [[ $RELATIVE -eq 1 ]]; then
  LINK_TARGET=$(python3 - "$DEST_PARENT" "$SOURCE" <<'PY'
import os,sys
print(os.path.relpath(sys.argv[2], start=sys.argv[1]))
PY
  )
fi

ln -s "$LINK_TARGET" "$DEST"
echo "Created symlink: $DEST -> $LINK_TARGET"

GI="$PROJECT/.gitignore"
ENTRY='github/instructions/shared/'
if [[ -f "$GI" ]]; then
  grep -Fxq "$ENTRY" "$GI" || { printf "\n%s\n" "$ENTRY" >> "$GI"; echo "Appended to .gitignore: $ENTRY"; }
else
  printf '# Added automatically to ignore shared instructions\n%s\n' "$ENTRY" > "$GI"; echo "Created .gitignore with entry: $ENTRY"
fi

echo "Done."

The macOS script and the Linux script are functionally identical. The logic, argument parsing, symlink creation, and .gitignore handling are the same. The scripts are kept separate for clarity and potential future customization, but currently, there are no substantive differences in their code or behavior.

Key Points:

  • Both scripts use Bash and similar flags (set -euo pipefail).
  • Argument parsing and validation are identical.
  • Symlink creation and .gitignore updates follow the same steps.
  • The only difference is the comment at the top indicating the target OS.

Gotcha: If you need OS-specific behavior (e.g., handling macOS quirks), you can customize the macOS script independently without affecting the Linux version.

Here is the Windows script generated from the prompt:

<#
Creates (or refreshes) a junction in a Project repo pointing to the shared ./instructions folder
inside this GitHubCopilot repo.

Junction (target created): <ProjectRepo>\github\instructions\shared
Points to (source):        <GitHubCopilotRepo>\instructions

Parameters:
  -GitHubCopilotRepo   Path to the GitHubCopilot repo root (where the ./instructions folder lives)
  -ProjectRepo         Path to the target Project repo root (where ./github/instructions/shared will be created)
  -Force               If specified, will remove any existing file/dir/junction at the destination

Steps performed:
 1. Validate paths
 2. Ensure Project repo ./github/instructions parent folders exist
 3. Ensure destination removed if Force (or is an existing junction to wrong target)
 4. Create junction via New-Item -ItemType Junction
 5. Add github/instructions/shared/ to Project .gitignore (create file if needed)

Usage examples:
  .\create-shared-link-windows.ps1 -GitHubCopilotRepo C:\git\GitHubCopilot -ProjectRepo C:\git\MyProject
  .\create-shared-link-windows.ps1 -GitHubCopilotRepo ..\GitHubCopilot -ProjectRepo . -Force

#>
[CmdletBinding(SupportsShouldProcess = $true)]
param(
    [Parameter(Mandatory = $true)] [string] $GitHubCopilotRepo,
    [Parameter(Mandatory = $true)] [string] $ProjectRepo,
    [switch] $Force
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

function Resolve-FullPath {
    param([string]$Path)
    return ([IO.Path]::GetFullPath((Resolve-Path -LiteralPath $Path).Path))
}

function Add-GitIgnoreEntry {
    param(
        [string]$RepoRoot,
        [string]$RelativeEntry
    )
    $gitIgnorePath = Join-Path $RepoRoot '.gitignore'
    if (-not (Test-Path $gitIgnorePath)) {
        "# Added automatically to ignore shared instructions`n$RelativeEntry`n" | Out-File -FilePath $gitIgnorePath -Encoding utf8 -NoNewline
        Write-Host "Created .gitignore and added entry: $RelativeEntry"
        return
    }
    $content = Get-Content $gitIgnorePath -Raw
    if ($content -notmatch [regex]::Escape($RelativeEntry)) {
        Add-Content -Path $gitIgnorePath -Value "`n$RelativeEntry" -Encoding utf8
        Write-Host "Appended to .gitignore: $RelativeEntry"
    }
    else {
        Write-Host ".gitignore already contains entry."        
    }
}

# Resolve absolute paths
$gitHubCopilotRoot = Resolve-FullPath $GitHubCopilotRepo
$projectRoot = Resolve-FullPath $ProjectRepo

$source = Join-Path $gitHubCopilotRoot 'instructions'
if (-not (Test-Path $source)) { throw "Source instructions folder not found: $source" }

$destParent = Join-Path $projectRoot 'github/instructions'
$dest = Join-Path $destParent 'shared'

if (-not (Test-Path $destParent)) {
    Write-Host "Creating parent directories: $destParent"
    New-Item -ItemType Directory -Path $destParent -Force | Out-Null
}

function Remove-ExistingDestination {
    if (Test-Path $dest) {
        $item = Get-Item -LiteralPath $dest -Force
        if ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) {
            Write-Host "Removing existing junction: $dest"
            Remove-Item -LiteralPath $dest -Force
        }
        else {
            if ($Force) {
                Write-Host "Removing existing non-junction item at destination due to -Force: $dest"
                Remove-Item -LiteralPath $dest -Recurse -Force
            }
            else {
                throw "Destination exists and is not a junction. Use -Force to remove: $dest"
            }
        }
    }
}

if (Test-Path $dest) {
    $existing = Get-Item -LiteralPath $dest -Force
    if ($existing.Attributes -band [IO.FileAttributes]::ReparsePoint) {
        # Try to read target (fsutil works on admin shells; fallback simply recreate if Force)
        $needsReplace = $false
        try {
            $targetInfo = (fsutil reparsepoint query $dest 2>$null)
            if ($targetInfo -match 'Print Name: (?<t>.+)$') {
                $currentTarget = $Matches['t']
                if ((Resolve-FullPath $currentTarget) -ne $source) { $needsReplace = $true }
            }
            else { $needsReplace = $true }
        }
        catch { $needsReplace = $true }
        if ($needsReplace -or $Force) {
            Remove-ExistingDestination
        }
        else {
            Write-Host "Junction already points to desired source. Nothing to do." -ForegroundColor Green
            Add-GitIgnoreEntry -RepoRoot $projectRoot -RelativeEntry 'github/instructions/shared/'
            return
        }
    }
    else {
        Remove-ExistingDestination
    }
}

Write-Host "Creating junction: $dest -> $source"
New-Item -ItemType Junction -Path $dest -Target $source | Out-Null
Write-Host "Junction created successfully." -ForegroundColor Green

Add-GitIgnoreEntry -RepoRoot $projectRoot -RelativeEntry 'github/instructions/shared/'

Write-Host "Done."

The Windows script (create-shared-link-windows.ps1) and the MacOS script (create-shared-link-macos.sh) both create a link from a project repo to a shared instructions folder, but they differ in several key ways aside from language and platform-specific commands.

Windows: Creates a NTFS junction using PowerShell's New-Item -ItemType Junction.
MacOS: Creates a symbolic link using ln -s in Bash.

Existing Destination Handling

Windows: Checks if the destination is a junction or another file type, removes or throws errors accordingly, supports a -Force flag.
MacOS: Removes existing destination with rm -rf if the –force flag is set.

Summary

The Windows script is more complex due to NTFS junctions and PowerShell features, while the MacOS script is simpler, relying on Unix symlinks and shell commands. Both achieve the same goal but are tailored to their respective operating systems.

Running the scripts requires appropriate permissions, especially for creating links and modifying directories. Users should ensure they have the necessary rights to execute these scripts in their environments.

This solution provides a simple way of sharing instruction files across multiple projects without duplication, ensuring consistency and ease of maintenance.

Feedback Loop

Feedback is always welcome. Please direct it to john.miller@codemag.com

Disclaimer

AI contributed to the writing to this blog post, but humans reviewed it, refined it, enhanced it and gave it soul.

Prompts:

  • Write a two paragraph introduction to GitHub copilot instruction files
  • /explain
  • Write a paragraph describing where instruction files can be located and the pros and cons of each location.
  • What is a symlink and junction point?
  • Describe the differences between the linux script and the maCOS script