-
Notifications
You must be signed in to change notification settings - Fork 849
Description
Bug: Custom agent discovery ignores symlinked files in agents directory
Bug Description
Custom agent files that are symbolic links to .md or .agent.md files are not discovered by the Copilot CLI. The agent discovery logic uses dirent.isFile() which returns false for symbolic links, even when those symlinks point to valid agent definition files.
This is problematic for users who maintain their agent definitions in a central repository and symlink them to ~/.copilot/agents/ for sharing across projects.
Affected Version
@github/copilot 1.0.5 (2025-01-07)
Node.js v22.13.0
Steps to Reproduce
-
Create a valid agent definition file:
echo "# Test Agent" > /tmp/test-agent.agent.md
-
Create a symbolic link in the agents directory:
ln -s /tmp/test-agent.agent.md ~/.copilot/agents/test-agent.agent.md -
Verify the symlink exists and points to a valid file:
ls -la ~/.copilot/agents/test-agent.agent.md # Shows: test-agent.agent.md -> /tmp/test-agent.agent.md cat ~/.copilot/agents/test-agent.agent.md # Shows: # Test Agent
-
Start Copilot CLI and check available agents - the symlinked agent is not listed.
-
Replace symlink with a hard link or copy:
rm ~/.copilot/agents/test-agent.agent.md cp /tmp/test-agent.agent.md ~/.copilot/agents/test-agent.agent.md
-
Restart Copilot CLI - the agent now appears.
Expected Behavior
Symbolic links to valid .md or .agent.md files should be discovered and loaded as custom agents, the same way regular files are.
Root Cause Analysis
The issue is in the WKn function (agent discovery) in the bundled index.js. The relevant code pattern:
// Current implementation (simplified/prettified from minified source)
async function discoverAgents(agentsDir) {
let entries = await readdir(agentsDir, { withFileTypes: true });
for (let entry of entries) {
// BUG: isFile() returns false for symbolic links
if (entry.isFile() && entry.name.endsWith(".md")) {
// Process agent...
}
}
}When using readdir with { withFileTypes: true }, the returned Dirent objects have type information. For symbolic links:
dirent.isFile()returnsfalsedirent.isSymbolicLink()returnstrue
This means symlinks are silently skipped even when they point to valid files.
Note: Interestingly, if the entire ~/.copilot/agents directory itself is a symlink to another directory, it works correctly because readdir(path) resolves path-level symlinks automatically. Only symlinked files within the directory are affected.
Suggested Fix
Option 1: Check for symlinks and follow them (Recommended)
async function discoverAgents(agentsDir) {
let entries = await readdir(agentsDir, { withFileTypes: true });
for (let entry of entries) {
if (!entry.name.endsWith(".md")) continue;
// Handle both regular files and symlinks to files
let isValidFile = entry.isFile();
if (entry.isSymbolicLink()) {
try {
const resolvedPath = path.join(agentsDir, entry.name);
const stat = await fs.stat(resolvedPath); // stat() follows symlinks
isValidFile = stat.isFile();
} catch {
// Broken symlink, skip it
continue;
}
}
if (isValidFile) {
// Process agent...
}
}
}Option 2: Use fs.stat() instead of dirent types
async function discoverAgents(agentsDir) {
let entries = await readdir(agentsDir); // Without withFileTypes
for (let name of entries) {
if (!name.endsWith(".md")) continue;
const filePath = path.join(agentsDir, name);
try {
const stat = await fs.stat(filePath); // Automatically follows symlinks
if (stat.isFile()) {
// Process agent...
}
} catch {
continue;
}
}
}Option 3: Minimal change - also accept symlinks
// Change this:
if (entry.isFile() && entry.name.endsWith(".md"))
// To this:
if ((entry.isFile() || entry.isSymbolicLink()) && entry.name.endsWith(".md"))Note: Option 3 is the simplest but doesn't verify that symlinks point to actual files (could accept symlinks to directories or broken symlinks).
Additional Context
- OS: Linux (WSL2) 6.6.87.2-microsoft-standard-WSL2
- Architecture: x86_64
- Shell: bash
- Terminal: Windows Terminal
Use Case
We maintain a shared repository of custom agent definitions that multiple team members use. The intended workflow is:
- Clone the shared agents repository
- Symlink individual agents to
~/.copilot/agents/ - Pull updates to get new/updated agents automatically
Currently, we must use hard links (which break on cross-filesystem setups and after git operations) or manually copy files (which don't auto-update).
Workarounds
- Hard links instead of symbolic links (same filesystem only, may break after git pulls)
- Copy files instead of symlinking (no auto-updates)
- Symlink the entire directory - make
~/.copilot/agentsitself a symlink to the source directory (works, but less flexible)