Skip to content

Conversation

@pwang347
Copy link
Member

@pwang347 pwang347 commented Jan 7, 2026

No description provided.

Copilot AI review requested due to automatic review settings January 7, 2026 01:22
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new /agentInfo slash command to help troubleshoot and diagnose Copilot customization settings. The command provides comprehensive diagnostics information about the user's customization setup in a single, accessible view.

Key Changes

  • Adds a new /agentInfo slash command that displays diagnostic information about instructions, skills, agents, and other customization files
  • Shows system information, workspace configuration, and active customization files with their paths
  • Implements progressive rendering using multiple progress.report calls to provide a responsive user experience
Comments suppressed due to low confidence (2)

src/vs/workbench/contrib/chat/browser/chat.contribution.ts:1273

  • The error object is directly concatenated into the markdown string without proper formatting or message extraction. This pattern is repeated throughout the function (lines 1273, 1306, 1342, 1380, 1416) and could expose internal error details to users. Consider using a consistent error formatting approach that extracts error messages safely.
				content += `*Error: ${e}*\n\n`;

src/vs/workbench/contrib/chat/browser/chat.contribution.ts:1202

  • The folder name is inserted directly into markdown table cells without escaping special characters. If the folder name contains pipe characters (|) or other markdown special characters, it will break the table formatting. This issue also appears in lines 1284, 1318-1320, 1353-1356, and 1394-1396. Consider escaping these values before inserting them into the markdown table.
				for (const folder of workspace.folders) {
					content += `| ${folder.name} | ${folder.uri.fsPath} |\n`;

Comment on lines +1185 to +1418
let content = '# Copilot Customization Diagnostics\n\n';
content += '*This shows where instructions, skills, agents, and other customization files are loaded from.*\n\n';

// System Information
content += '## System Information\n\n';
content += `| Property | Value |\n`;
content += `|----------|-------|\n`;
content += `| Generated | ${new Date().toISOString()} |\n`;
content += `| Product | ${productService.nameLong} ${productService.version} |\n`;
content += `| Commit | ${productService.commit || 'n/a'} |\n\n`;

// Workspace Information
content += '## Workspace Information\n\n';
if (workspace.folders.length > 0) {
content += '| Folder | Path |\n';
content += '|--------|------|\n';
for (const folder of workspace.folders) {
content += `| ${folder.name} | ${folder.uri.fsPath} |\n`;
}
content += '\n';
} else {
content += '*No workspace folders open*\n\n';
}

content += `**User Home Directory**: \`${userHome.fsPath}\`\n\n`;

// Configuration Settings
content += '## Configuration Settings\n\n';
content += '| Setting | Value |\n';
content += '|---------|-------|\n';
content += `| ${PromptsConfig.USE_COPILOT_INSTRUCTION_FILES} | ${configurationService.getValue(PromptsConfig.USE_COPILOT_INSTRUCTION_FILES)} |\n`;
content += `| ${PromptsConfig.USE_AGENT_MD} | ${configurationService.getValue(PromptsConfig.USE_AGENT_MD)} |\n`;
content += `| ${PromptsConfig.USE_NESTED_AGENT_MD} | ${configurationService.getValue(PromptsConfig.USE_NESTED_AGENT_MD)} |\n`;
content += `| ${PromptsConfig.USE_AGENT_SKILLS} | ${configurationService.getValue(PromptsConfig.USE_AGENT_SKILLS)} |\n\n`;

progress.report({ content: new MarkdownString(content), kind: 'markdownContent' });

// Instruction Files Section
content = '## Instruction Files\n\n';
content += '### Lookup Paths\n\n';
const instructionLocations = PromptsConfig.promptSourceFolders(configurationService, PromptsType.instructions);
if (instructionLocations.length === 0) {
instructionLocations.push(INSTRUCTIONS_DEFAULT_SOURCE_FOLDER);
}
content += '| Source | Path |\n';
content += '|--------|------|\n';
for (const folder of workspace.folders) {
for (const location of instructionLocations) {
content += `| Workspace: ${folder.name} | \`${folder.uri.fsPath}/${location}\` |\n`;
}
}
content += `| User Data | (Stored in VS Code user data profile) |\n\n`;

content += '### Active Instruction Files\n\n';
try {
const instructionFiles = await promptsService.listPromptFiles(PromptsType.instructions, token);
if (instructionFiles.length > 0) {
content += '| Name | Storage | Path |\n';
content += '|------|---------|------|\n';
for (const file of instructionFiles) {
const label = promptsService.getPromptLocationLabel(file);
content += `| ${file.uri.path.split('/').pop()} | ${file.storage} | ${label} |\n`;
}
content += '\n';
} else {
content += '*No instruction files found*\n\n';
}
} catch (e) {
content += `*Error loading instruction files: ${e}*\n\n`;
}

progress.report({ content: new MarkdownString(content), kind: 'markdownContent' });

// Copilot Instructions
content = '### Copilot Instructions (.github/copilot-instructions.md)\n\n';
try {
const copilotInstructions = await promptsService.listCopilotInstructionsMDs(token);
if (copilotInstructions.length > 0) {
content += '| Path |\n';
content += '|------|\n';
for (const uri of copilotInstructions) {
content += `| \`${uri.fsPath}\` |\n`;
}
content += '\n';
} else {
content += '*No copilot-instructions.md files found*\n\n';
}
} catch (e) {
content += `*Error: ${e}*\n\n`;
}

progress.report({ content: new MarkdownString(content), kind: 'markdownContent' });

// AGENTS.md Files
content = '## AGENTS.md Files\n\n';
content += '### Lookup Paths\n\n';
content += '| Source | Path |\n';
content += '|--------|------|\n';
for (const folder of workspace.folders) {
content += `| Workspace Root: ${folder.name} | \`${folder.uri.fsPath}/AGENTS.md\` |\n`;
}
if (configurationService.getValue(PromptsConfig.USE_NESTED_AGENT_MD)) {
content += `| Nested (all subfolders) | \`**/AGENTS.md\` |\n`;
}
content += '\n';

content += '### Active AGENTS.md Files\n\n';
try {
const includeNested = configurationService.getValue<boolean>(PromptsConfig.USE_NESTED_AGENT_MD) ?? false;
const agentMDs = await promptsService.listAgentMDs(token, includeNested);
if (agentMDs.length > 0) {
content += '| Path |\n';
content += '|------|\n';
for (const uri of agentMDs) {
content += `| \`${uri.fsPath}\` |\n`;
}
content += '\n';
} else {
content += '*No AGENTS.md files found*\n\n';
}
} catch (e) {
content += `*Error: ${e}*\n\n`;
}

progress.report({ content: new MarkdownString(content), kind: 'markdownContent' });

// Custom Agents Section
content = '## Custom Agents\n\n';
content += '### Lookup Paths\n\n';
const agentLocations = PromptsConfig.promptSourceFolders(configurationService, PromptsType.agent);
agentLocations.push(AGENTS_SOURCE_FOLDER);
content += '| Source | Path |\n';
content += '|--------|------|\n';
for (const folder of workspace.folders) {
for (const location of [...new Set(agentLocations)]) {
content += `| Workspace: ${folder.name} | \`${folder.uri.fsPath}/${location}\` |\n`;
}
}
content += `| User Data | (Stored in VS Code user data profile) |\n\n`;

content += '### Active Custom Agents\n\n';
try {
const customAgents = await promptsService.getCustomAgents(token);
if (customAgents.length > 0) {
content += '| Name | Description | Source | Path |\n';
content += '|------|-------------|--------|------|\n';
for (const agent of customAgents) {
const sourceLabel = agent.source.storage === 'extension'
? `Extension: ${agent.source.extensionId.value}`
: agent.source.storage;
content += `| ${agent.name} | ${agent.description || '-'} | ${sourceLabel} | \`${agent.uri.fsPath}\` |\n`;
}
content += '\n';
} else {
content += '*No custom agents found*\n\n';
}
} catch (e) {
content += `*Error: ${e}*\n\n`;
}

progress.report({ content: new MarkdownString(content), kind: 'markdownContent' });

// Agent Skills Section
content = '## Agent Skills\n\n';
content += '### Lookup Paths\n\n';
content += '**Workspace Skills:**\n\n';
content += '| Source | Path |\n';
content += '|--------|------|\n';
for (const folder of workspace.folders) {
for (const { path, type } of DEFAULT_AGENT_SKILLS_WORKSPACE_FOLDERS) {
content += `| ${type} | \`${folder.uri.fsPath}/${path}/<skill-name>/SKILL.md\` |\n`;
}
}
content += '\n**Personal Skills (User Home):**\n\n';
content += '| Source | Path |\n';
content += '|--------|------|\n';
for (const { path, type } of DEFAULT_AGENT_SKILLS_USER_HOME_FOLDERS) {
content += `| ${type} | \`${userHome.fsPath}/${path}/<skill-name>/SKILL.md\` |\n`;
}
content += '\n';

content += '### Active Agent Skills\n\n';
try {
const skills = await promptsService.findAgentSkills(CancellationToken.None);
if (skills && skills.length > 0) {
content += '| Name | Description | Type | Path |\n';
content += '|------|-------------|------|------|\n';
for (const skill of skills) {
content += `| ${skill.name} | ${skill.description || '-'} | ${skill.type} | \`${skill.uri.fsPath}\` |\n`;
}
content += '\n';
} else {
content += '*No agent skills found (skills may be disabled via settings)*\n\n';
}
} catch (e) {
content += `*Error: ${e}*\n\n`;
}

progress.report({ content: new MarkdownString(content), kind: 'markdownContent' });

// Prompt Files Section
content = '## Prompt Files\n\n';
content += '### Lookup Paths\n\n';
const promptLocations = PromptsConfig.promptSourceFolders(configurationService, PromptsType.prompt);
if (promptLocations.length === 0) {
promptLocations.push(PROMPT_DEFAULT_SOURCE_FOLDER);
}
content += '| Source | Path |\n';
content += '|--------|------|\n';
for (const folder of workspace.folders) {
for (const location of promptLocations) {
content += `| Workspace: ${folder.name} | \`${folder.uri.fsPath}/${location}\` |\n`;
}
}
content += `| User Data | (Stored in VS Code user data profile) |\n\n`;

content += '### Active Prompt Files\n\n';
try {
const promptFiles = await promptsService.listPromptFiles(PromptsType.prompt, token);
if (promptFiles.length > 0) {
content += '| Name | Storage | Path |\n';
content += '|------|---------|------|\n';
for (const file of promptFiles) {
const label = promptsService.getPromptLocationLabel(file);
content += `| ${file.uri.path.split('/').pop()} | ${file.storage} | ${label} |\n`;
}
content += '\n';
} else {
content += '*No prompt files found*\n\n';
}
} catch (e) {
content += `*Error: ${e}*\n\n`;
}

Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All user-facing strings in the markdown output should be localized using the nls.localize() method. The strings include section headers, table headers, messages, and labels. This is consistent with VS Code's localization requirements where all strings visible to users must be externalized.

Copilot uses AI. Check for mistakes.

content += '### Active Agent Skills\n\n';
try {
const skills = await promptsService.findAgentSkills(CancellationToken.None);
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function call uses CancellationToken.None instead of the token parameter passed to the slash command handler. This is inconsistent with other async calls in the same function that properly use the token parameter (lines 1240, 1261, 1294, 1327, 1403). Using the provided token allows for proper cancellation handling when the user cancels the operation.

Suggested change
const skills = await promptsService.findAgentSkills(CancellationToken.None);
const skills = await promptsService.findAgentSkills(token);

Copilot uses AI. Check for mistakes.
Comment on lines +1233 to +1246
content += `| Workspace: ${folder.name} | \`${folder.uri.fsPath}/${location}\` |\n`;
}
}
content += `| User Data | (Stored in VS Code user data profile) |\n\n`;

content += '### Active Instruction Files\n\n';
try {
const instructionFiles = await promptsService.listPromptFiles(PromptsType.instructions, token);
if (instructionFiles.length > 0) {
content += '| Name | Storage | Path |\n';
content += '|------|---------|------|\n';
for (const file of instructionFiles) {
const label = promptsService.getPromptLocationLabel(file);
content += `| ${file.uri.path.split('/').pop()} | ${file.storage} | ${label} |\n`;
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The folder name and file names are inserted directly into markdown table cells without escaping special characters. If these names contain pipe characters (|), backticks (`), or other markdown special characters, they will break the table formatting. Consider escaping these values before inserting them into the markdown table.

This issue also appears in the following locations of the same file:

  • line 1201

Copilot uses AI. Check for mistakes.
Comment on lines +1316 to +1319
content += '| Source | Path |\n';
content += '|--------|------|\n';
for (const folder of workspace.folders) {
for (const location of [...new Set(agentLocations)]) {
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creating a new Set on every iteration of the outer loop is inefficient. The Set should be created once before the loops begin. Move the Set creation outside the nested loops to avoid creating multiple Sets unnecessarily.

Suggested change
content += '| Source | Path |\n';
content += '|--------|------|\n';
for (const folder of workspace.folders) {
for (const location of [...new Set(agentLocations)]) {
const uniqueAgentLocations = [...new Set(agentLocations)];
content += '| Source | Path |\n';
content += '|--------|------|\n';
for (const folder of workspace.folders) {
for (const location of uniqueAgentLocations) {

Copilot uses AI. Check for mistakes.
Comment on lines +1252 to +1253
} catch (e) {
content += `*Error loading instruction files: ${e}*\n\n`;
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error object is directly concatenated into the markdown string without proper formatting or message extraction. This could expose internal error stack traces or object representations to users. Consider using a more user-friendly error message format, such as extracting the error message with proper checks for Error instances.

This issue also appears in the following locations of the same file:

  • line 1273

Copilot uses AI. Check for mistakes.
Comment on lines +1331 to +1335
for (const agent of customAgents) {
const sourceLabel = agent.source.storage === 'extension'
? `Extension: ${agent.source.extensionId.value}`
: agent.source.storage;
content += `| ${agent.name} | ${agent.description || '-'} | ${sourceLabel} | \`${agent.uri.fsPath}\` |\n`;
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The agent name and description are inserted directly into markdown table cells without escaping special characters. If these values contain pipe characters (|), backticks (`), or other markdown special characters, they will break the table formatting. This same issue exists in the skills table (lines 1372-1373) and prompt files table (lines 1407-1409). Consider escaping these values before inserting them into the markdown table.

Copilot uses AI. Check for mistakes.
Comment on lines +1402 to 1423
try {
const promptFiles = await promptsService.listPromptFiles(PromptsType.prompt, token);
if (promptFiles.length > 0) {
content += '| Name | Storage | Path |\n';
content += '|------|---------|------|\n';
for (const file of promptFiles) {
const label = promptsService.getPromptLocationLabel(file);
content += `| ${file.uri.path.split('/').pop()} | ${file.storage} | ${label} |\n`;
}
content += '\n';
} else {
content += '*No prompt files found*\n\n';
}
} catch (e) {
content += `*Error: ${e}*\n\n`;
}

progress.report({ content: new MarkdownString(content), kind: 'markdownContent' });

await timeout(200);
}));
}
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This large function (over 240 lines) could be refactored into smaller, focused helper functions. Consider extracting sections like "System Information", "Workspace Information", "Instruction Files", "Custom Agents", "Agent Skills", and "Prompt Files" into separate private methods. This would improve readability, testability, and maintainability.

Suggested change
try {
const promptFiles = await promptsService.listPromptFiles(PromptsType.prompt, token);
if (promptFiles.length > 0) {
content += '| Name | Storage | Path |\n';
content += '|------|---------|------|\n';
for (const file of promptFiles) {
const label = promptsService.getPromptLocationLabel(file);
content += `| ${file.uri.path.split('/').pop()} | ${file.storage} | ${label} |\n`;
}
content += '\n';
} else {
content += '*No prompt files found*\n\n';
}
} catch (e) {
content += `*Error: ${e}*\n\n`;
}
progress.report({ content: new MarkdownString(content), kind: 'markdownContent' });
await timeout(200);
}));
}
content = await this.appendPromptFilesSection(content, promptsService, token);
progress.report({ content: new MarkdownString(content), kind: 'markdownContent' });
await timeout(200);
}));
}
private async appendPromptFilesSection(content: string, promptsService: IPromptsService, token: CancellationToken): Promise<string> {
try {
const promptFiles = await promptsService.listPromptFiles(PromptsType.prompt, token);
if (promptFiles.length > 0) {
content += '| Name | Storage | Path |\n';
content += '|------|---------|------|\n';
for (const file of promptFiles) {
const label = promptsService.getPromptLocationLabel(file);
content += `| ${file.uri.path.split('/').pop()} | ${file.storage} | ${label} |\n`;
}
content += '\n';
} else {
content += '*No prompt files found*\n\n';
}
} catch (e) {
content += `*Error: ${e}*\n\n`;
}
return content;
}

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants