The PostToolUse hook in hooks/vale-edit.sh passes .tool_input.file_path to Vale unchanged. Claude Code always supplies that path absolute. Vale matches a path-scoped section against the file as it was asked for it, so a section like [docs/**/*.md] does not match an absolute path, and Vale reports nothing when it fails to match.
The result is that on a repository whose rules are path-scoped, the hook runs, exits 0, prints nothing, and looks exactly like a clean pass on a file it did not really check.
Checked against dcef76f (the installed plugin copy is byte-identical to it) with Vale 3.18.0 on Linux.
Reproduction
# .vale.ini
StylesPath = styles
MinAlertLevel = suggestion
[*.md]
BasedOnStyles =
[docs/**/*.md]
BasedOnStyles = MyStyle
# styles/MyStyle/Rule.yml
extends: existence
message: "found 'zzqq'"
level: error
tokens:
- 'zzqq'
With docs/page.md containing A line containing zzqq here.:
$ cd /repo && vale --no-global --output=line docs/page.md
docs/page.md:1:19:MyStyle.Rule:found 'zzqq' # exit 1
$ cd /repo && vale --no-global --output=line /repo/docs/page.md
# no output, exit 0
The second form is what the hook produces. Feeding the hook a payload for that same file returns nothing at all.
Suggested fix
Resolve the payload path against the repository root and run Vale from there:
REPO="$(git rev-parse --show-toplevel 2>/dev/null)" || exit 0
case "$file" in
"$REPO"/*) rel="${file#"$REPO"/}" ;;
/*) exit 0 ;;
*) rel="$file" ;;
esac
alerts=$(cd "$REPO" && vale --no-exit --output=JSON "$rel")
A second, smaller point
The hook sends Vale's stderr to /dev/null. A configuration error (E201, E100) leaves stdout empty, so a config that fails to load is indistinguishable from a clean run. Reporting "Vale did not run" as its own outcome would make that visible instead of silently reassuring. Happy to split that into its own issue if you prefer.
Thanks for the plugin. The rest of the design is good, and the error-level-only choice in particular is the right call.
The
PostToolUsehook inhooks/vale-edit.shpasses.tool_input.file_pathto Vale unchanged. Claude Code always supplies that path absolute. Vale matches a path-scoped section against the file as it was asked for it, so a section like[docs/**/*.md]does not match an absolute path, and Vale reports nothing when it fails to match.The result is that on a repository whose rules are path-scoped, the hook runs, exits 0, prints nothing, and looks exactly like a clean pass on a file it did not really check.
Checked against
dcef76f(the installed plugin copy is byte-identical to it) with Vale 3.18.0 on Linux.Reproduction
With
docs/page.mdcontainingA line containing zzqq here.:The second form is what the hook produces. Feeding the hook a payload for that same file returns nothing at all.
Suggested fix
Resolve the payload path against the repository root and run Vale from there:
A second, smaller point
The hook sends Vale's stderr to
/dev/null. A configuration error (E201,E100) leaves stdout empty, so a config that fails to load is indistinguishable from a clean run. Reporting "Vale did not run" as its own outcome would make that visible instead of silently reassuring. Happy to split that into its own issue if you prefer.Thanks for the plugin. The rest of the design is good, and the error-level-only choice in particular is the right call.