Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (4)
src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts:405
- The hook-event insertion snippet on empty lines uses
\n - .../\n command: ..., which is only correct if the hook key is at column 1. Inside ahooks:map the key is indented, so the subsequent lines should be indented relative to that (e.g.\n - ...and\n command: ...). Adjust the snippet indentation based on the hook key indentation to avoid generating malformed YAML.
This issue also appears on line 314 of the same file.
if (isEmptyLine) {
// On empty lines, insert a full hook snippet with command placeholder
insertText = [
`${hookName}:`,
` - type: command`,
` command: "$1"`,
].join('\n');
src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts:660
validateHookCommandtreatsbash/powershellas command-providing fields for non-Copilot targets (validCommandFieldsincludes them), but the missing-command error message says the command must specify one ofcommand,windows,linux, orosx. Either removebash/powershellfrom the accepted command fields for VS Code agents, or update the error message to include them so validation feedback matches what the parser/runtime accepts.
// Determine valid and command-providing properties based on target
const validCommandFields = isCopilotCli
? new Set(['bash', 'powershell'])
: new Set(['command', 'windows', 'linux', 'osx', 'bash', 'powershell']);
const validProperties = isCopilotCli
? new Set(['type', 'bash', 'powershell', 'cwd', 'env', 'timeoutSec'])
: new Set(['type', 'command', 'windows', 'linux', 'osx', 'bash', 'powershell', 'cwd', 'env', 'timeout']);
let hasType = false;
let hasCommandField = false;
for (const prop of item.properties) {
const key = prop.key.value;
if (!validProperties.has(key)) {
report(toMarker(localize('promptValidator.unknownHookProperty', "Unknown property '{0}' in hook command.", key), prop.key.range, MarkerSeverity.Warning));
}
if (key === 'type') {
hasType = true;
if (prop.value.type !== 'scalar' || prop.value.value !== 'command') {
report(toMarker(localize('promptValidator.hookTypeMustBeCommand', "The 'type' property in a hook command must be 'command'."), prop.value.range, MarkerSeverity.Error));
}
} else if (validCommandFields.has(key)) {
hasCommandField = true;
if (prop.value.type !== 'scalar' || prop.value.value.trim().length === 0) {
report(toMarker(localize('promptValidator.hookCommandFieldMustBeNonEmptyString', "The '{0}' property in a hook command must be a non-empty string.", key), prop.value.range, MarkerSeverity.Error));
}
} else if (key === 'cwd') {
if (prop.value.type !== 'scalar') {
report(toMarker(localize('promptValidator.hookCwdMustBeString', "The 'cwd' property in a hook command must be a string."), prop.value.range, MarkerSeverity.Error));
}
} else if (key === 'env') {
if (prop.value.type !== 'map') {
report(toMarker(localize('promptValidator.hookEnvMustBeMap', "The 'env' property in a hook command must be a map of string values."), prop.value.range, MarkerSeverity.Error));
} else {
for (const envProp of prop.value.properties) {
if (envProp.value.type !== 'scalar') {
report(toMarker(localize('promptValidator.hookEnvValueMustBeString', "Environment variable '{0}' must have a string value.", envProp.key.value), envProp.value.range, MarkerSeverity.Error));
}
}
}
} else if (key === 'timeout' || key === 'timeoutSec') {
if (prop.value.type !== 'scalar' || isNaN(Number(prop.value.value))) {
report(toMarker(localize('promptValidator.hookTimeoutMustBeNumber', "The '{0}' property in a hook command must be a number.", key), prop.value.range, MarkerSeverity.Error));
}
}
}
if (!hasType) {
report(toMarker(localize('promptValidator.hookMissingType', "Hook command is missing required property 'type'."), item.range, MarkerSeverity.Error));
}
if (!hasCommandField) {
if (isCopilotCli) {
report(toMarker(localize('promptValidator.hookMissingCopilotCommand', "Hook command must specify at least one of 'bash' or 'powershell'."), item.range, MarkerSeverity.Error));
} else {
report(toMarker(localize('promptValidator.hookMissingCommand', "Hook command must specify at least one of 'command', 'windows', 'linux', or 'osx'."), item.range, MarkerSeverity.Error));
}
}
src/vs/workbench/contrib/chat/common/promptSyntax/hookSchema.ts:541
normalizeForResolveforcestype: 'command'whentypeis omitted. In agent frontmatter, the new validator flags missingtypeas an error, butparseSubagentHooksFromYamlwill still resolve and execute these hooks at runtime. Consider aligning runtime parsing with validation (e.g., only default the type when parsing Claude-style nested matcher entries / Claude target, or otherwise require explicittype).
/**
* Normalizes a hook command object for resolving.
* Claude format allows omitting the 'type' field, treating it as 'command'.
* This ensures compatibility when Claude-style hooks are pasted into Copilot format.
*/
function normalizeForResolve(raw: Record<string, unknown>): Record<string, unknown> {
// If type is missing or already 'command', ensure it's set to 'command'
if (raw.type === undefined || raw.type === 'command') {
return { ...raw, type: 'command' };
}
return raw;
src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptHeaderAutocompletion.ts:322
- Hook event completions insert YAML snippets with fixed indentation (
' - type: command',' command: ...'). When completing inside thehooks:map (where keys are already indented), this produces invalid indentation (the list item should be indented deeper than the hook key). Consider computing the base indent from the current line / hook key indent and emitting-/field lines with the correct additional indentation.
const lineText = model.getLineContent(position.lineNumber);
const colonIdx = lineText.indexOf(':');
if (colonIdx !== -1 && position.column > colonIdx + 1) {
const whilespaceAfterColon = (lineText.substring(colonIdx + 1).match(/^\s*/)?.[0].length) ?? 0;
const commandSnippet = [
'',
' - type: command',
' command: "$1"',
].join('\n');
dmitrivMS
approved these changes
Mar 4, 2026
This was referenced Mar 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
CC @aeschli