What is dark
uphold hook <harness> is the MCP seam: it stands in for the shim when an agent
publishes through an MCP server, where there is no process to intercept and no
argv[0] to decide by. It consults text::failures (the literal rules plus the
running-host fallback) and guard::over_text (the text-capable built-in
guards). It never consults prose::over_text.
text::failures_in deliberately excludes the prose rules, and says so: folding
them in would report each of them twice at the shim seam, where text-literals
is a checker in its own right. Every other whole-text seam therefore carries a
compensating call to prose::over_text beside the literal or guard dispatch --
text::check for uphold scan --text, guard_command in main.rs for
uphold guard --text. The hook seam was never given one.
So every prose_regexp rule is dark at uphold hook. That includes the whole
bundled prose-shapes set and the prose half of published-text -- sets whose
rules declare command.before = ["gh", "git push"] precisely because the text
they judge is a pull-request body, an issue title, a release note. Those are
exactly what an agent posts through mcp__github__*.
Reproduce
policy/principles.toml:
[rule.no-empty-hedge-here]
message = "State the claim."
prose_regexp = '(?i)\barguably\b'
files.include = ["."]
command.before = ["gh"]
[[shim]]
command = "gh"
match = ["pr:create"]
text_flags = ["-b", "--body"]
$ echo '{"tool_name":"mcp__github__create_pull_request","tool_input":{"body":"This is arguably fine."}}' | uphold hook claude-code
$ echo $?
0
$ printf 'This is arguably fine.\n' | uphold scan --text -
policy check failed: no-empty-hedge-here
State the claim.
line 1: This is arguably fine.
$ echo $?
1
The same sentence is refused when git commit records it and allowed when an
MCP server publishes it.
Why it happened, and the fix that is not a fifth copy
There are four assemblies of the same ingredients -- literal rules, text
guards, prose rules -- in text::check, main::guard_command --text,
hook::run, and the shim's text path. Each one is hand-assembled, so a rule
kind added to three of them is dark in the fourth with nothing to say so. The
prose call reached three seams and missed this one; the same shape is already
visible one layer down, where the shim's editor path dispatches regexp and
require_regexp to its pattern checker and leaves prose_regexp to fall
through to the exec consultation, which errors out as a dispatch hole.
The fix is to hoist "everything a piece of published text is judged by" into
one function, with one table saying which seam consults which kind, and have
every seam reach it -- so a future rule kind cannot be added to three of four.
Documentation
docs/REFERENCE.md currently records the exclusion as deliberate, on the
grounds that a tool call carries joined argument strings rather than a
paragraph. That holds for a call carrying flags and ids; it does not hold for
the field an agent puts a pull-request body in, which is the field these sets
were written for. The section needs to say that prose rules are consulted.
What is dark
uphold hook <harness>is the MCP seam: it stands in for the shim when an agentpublishes through an MCP server, where there is no process to intercept and no
argv[0]to decide by. It consultstext::failures(the literal rules plus therunning-host fallback) and
guard::over_text(the text-capable built-inguards). It never consults
prose::over_text.text::failures_indeliberately excludes the prose rules, and says so: foldingthem in would report each of them twice at the shim seam, where
text-literalsis a checker in its own right. Every other whole-text seam therefore carries a
compensating call to
prose::over_textbeside the literal or guard dispatch --text::checkforuphold scan --text,guard_commandinmain.rsforuphold guard --text. The hook seam was never given one.So every
prose_regexprule is dark atuphold hook. That includes the wholebundled
prose-shapesset and the prose half ofpublished-text-- sets whoserules declare
command.before = ["gh", "git push"]precisely because the textthey judge is a pull-request body, an issue title, a release note. Those are
exactly what an agent posts through
mcp__github__*.Reproduce
policy/principles.toml:The same sentence is refused when
git commitrecords it and allowed when anMCP server publishes it.
Why it happened, and the fix that is not a fifth copy
There are four assemblies of the same ingredients -- literal rules, text
guards, prose rules -- in
text::check,main::guard_command --text,hook::run, and the shim's text path. Each one is hand-assembled, so a rulekind added to three of them is dark in the fourth with nothing to say so. The
prose call reached three seams and missed this one; the same shape is already
visible one layer down, where the shim's editor path dispatches
regexpandrequire_regexpto its pattern checker and leavesprose_regexpto fallthrough to the
execconsultation, which errors out as a dispatch hole.The fix is to hoist "everything a piece of published text is judged by" into
one function, with one table saying which seam consults which kind, and have
every seam reach it -- so a future rule kind cannot be added to three of four.
Documentation
docs/REFERENCE.mdcurrently records the exclusion as deliberate, on thegrounds that a tool call carries joined argument strings rather than a
paragraph. That holds for a call carrying flags and ids; it does not hold for
the field an agent puts a pull-request body in, which is the field these sets
were written for. The section needs to say that prose rules are consulted.