Skip to content
Merged
2 changes: 1 addition & 1 deletion plugins/claude-config/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "claude-config",
"version": "0.27.2",
"version": "0.27.3",
"description": "Seven configuration-health skills (plus setup) for a repo's Claude Code configuration: audit (settings.json / .mcp.json / hooks / plugins / permissions drift), audit-automation-gaps (evidence-gated verdicts on automation gaps), audit-permission-grants (allow-rule / allowed-tools grants for auto-mode durability and portability), audit-instructions (locally-owned instruction surfaces vs current model capability — proposes removals/rewrites of instructions the model no longer needs, and detects cross-surface instruction conflicts), audit-prompting-postures (the additive lane — posture guidance the prompting guide says a component's purpose needs but the component does not carry), audit-pass (one coordinated, ordered, resumable pass over a named target — three-scope inventory, run-time-derived exclusion set, stable finding identity, suppression memory, resume, one human gate — delegating every check to the plugin that owns it), and unhobble (the empirical bare-baseline experiment: reversibly strip a repo's standing instructions, log real stumbles against the current model, re-add only what evidence earns).",
"author": {
"name": "Melodic Software",
Expand Down
19 changes: 19 additions & 0 deletions plugins/claude-config/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,25 @@
All notable changes to the `claude-config` plugin are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning.

## [0.27.3]

### Fixed
Comment thread
kyle-sexton marked this conversation as resolved.

- **`audit-instructions`: the hook-event blockability partition in `conflict-criteria.md` was
closed** (conflict-criteria 1.3.0 → 1.4.0; issue #1989 row 244). The exit-2 bullet enumerated six
"blockable" and five "non-blockable" events as an exhaustive split, while the hooks page's
"Exit code 2 behavior per event" table documents far more — including five events this repository's
own hooks already register (`ConfigChange` and `PostToolBatch` block; `StopFailure`,
`PermissionDenied`, and `InstructionsLoaded` have their exit code ignored), every one of them
ungradeable under the old text. The bullet now defers to that table as the sole authority and
restates none of its rows: resolve the handler's event, read its row, and pair on the row's own
`Can block?` cell — taking the paired content from what the row states is prevented rather than
assuming a tool call or a prompt, and recording an event with no row (or an unreachable table) as
`blockability-unresolved` instead of inferring it. The `SubagentStop` subagent-scoping rule and
the `PostToolUse`/`PreToolUse` worked pair are kept as examples. The file's recheck trigger no
longer fires on a row added to the upstream table, and `evals/evals.json` eval 16 now tests the
lookup procedure rather than the memorized split.

## [0.27.2]

### Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,11 +187,11 @@
"id": 16,
"name": "post-tool-use-exit-2-is-not-a-gate",
"prompt": "/claude-config:audit-instructions conflicts — my settings.json has a PostToolUse hook on Write that runs a linter and exits 2 with \"markdown lint failed: do not write this file\" on stderr, and my project CLAUDE.md instructs Claude to use the Write tool to create new docs. Is that a conflict?",
"expected_output": "Reports NO conflict, and gives the event-specific reason. Exit 2 is turn-scoped error feedback, never a standing directive, and whether it also carries a gate depends on the event: the hooks page's per-event exit-2 table makes PostToolUse non-blocking — the tool already ran — alongside Notification, SubagentStart, SessionStart, and SessionEnd, while exit 2 does block on PreToolUse, UserPromptSubmit, Stop, SubagentStop, PreCompact, and UserPromptExpansion. Because PostToolUse blocks nothing, there is no act for the stderr to stand in for and no gate to pair, so the message is transient feedback that pairs as nothing. Treating the stderr as a prohibition on Write would manufacture an unsatisfiable conflict against a CLAUDE.md that requires Write — the write already happened and the hook can neither block nor undo it. Had the same handler been registered on PreToolUse, it WOULD enter the comparison set as the act it blocks.",
"expected_output": "Reports NO conflict, and gives the event-specific reason. Exit 2 is turn-scoped error feedback, never a standing directive, and whether it also carries a gate depends on the event, resolved by reading that event's row in the hooks page's \"Exit code 2 behavior per event\" table rather than from a memorized set of events. PostToolUse's row says it cannot block — the tool already ran — so there is no act for the stderr to stand in for and no gate to pair, and the message is transient feedback that pairs as nothing. Treating the stderr as a prohibition on Write would manufacture an unsatisfiable conflict against a CLAUDE.md that requires Write — the write already happened and the hook can neither block nor undo it. Had the same handler been registered on PreToolUse, whose row blocks the tool call, it WOULD enter the comparison set as the act that row states is prevented.",
"files": [],
"expectations": [
"Reports no conflict, on the ground that PostToolUse exit 2 blocks nothing because the tool already ran — not by treating every exit-2 stderr message as the act it blocks",
"Resolves blockability from the event before applying the gate abstraction, and gets the partition right: PreToolUse, UserPromptSubmit, Stop, SubagentStop, PreCompact, and UserPromptExpansion block, while PostToolUse, Notification, SubagentStart, SessionStart, and SessionEnd do not",
"Resolves blockability from the handler's own event row in the upstream per-event exit-2 table before applying the gate abstraction, rather than from a fixed list of blocking and non-blocking events, and takes the paired content from what that row states is prevented",
"States that the same handler on a blockable event such as PreToolUse would enter the comparison set as the act it blocks, so the non-blocking case is a scoped exclusion rather than a blanket exemption for exit-2 hooks"
]
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Cross-Surface Conflict Criteria

Version: 1.3.0
Last updated: 2026-07-31
Version: 1.4.0
Last updated: 2026-08-08

**The adjudication procedure for check I15.** [criteria.md](criteria.md)'s I15 entry owns the
definition — what a cross-surface conflict *is*, its comparison set, its import and symlink
Expand All @@ -19,8 +19,11 @@ precedence or load-order text; a change to the skills page's statements about in
any new instruction surface added to the product; a change to how permission rules or permission
modes remove a tool from Claude's pool; a change to **which hook events inject handler output into
the session's context**, to the events `additionalContext` is accepted on, or to the handler types
that can return it; a change to **which events exit 2 can actually block** in the hooks page's
per-event exit-2 table, or to the set of locations a hook may be declared in.
that can return it; the removal, renaming, or restructuring of the hooks page's **per-event exit-2
table** this file defers to for blockability, or a change to the set of locations a hook may be
declared in; a change to the table's `SubagentStop`, `PostToolUse`, or `PreToolUse` rows — the
three the worked examples below cite. A row added to the table, or a change to any row this file
does not cite, needs no recheck — the partition itself is never restated here.

## Sources

Expand Down Expand Up @@ -209,21 +212,31 @@ Three consequences for residency, and each one bounds a pair rather than admitti
- **Exit-2 stderr is turn-scoped error feedback, not a standing directive — and only some events
have an act to block.** "Exit 2 means a blocking error … stderr text is fed back to Claude as an
error message." It reaches Claude, so it is not nothing; but it is a one-turn message, never a
standing rule. Whether it also carries a *gate* is event-specific, and the hooks page's per-event
exit-2 table settles it — resolve the handler's event before applying any gate abstraction:
- **Blockable events** — `PreToolUse`, `UserPromptSubmit`, `Stop`, `SubagentStop`, `PreCompact`,
and `UserPromptExpansion`. Here exit 2 does prevent something, so the conflict-bearing content
is the act it blocks — the treatment the prompt-hook bullets above already give. `SubagentStop`
is blockable but subagent-scoped: its act pairs inside the subagent, under the subagent-scoping
standing rule. Whether it also carries a *gate* is event-specific, and the sole authority on that
is the hooks page's
[Exit code 2 behavior per event](https://code.claude.com/docs/en/hooks#exit-code-2-behavior-per-event)
table. Its rows are not reproduced here in either direction: the event set grows, so any list
copied into this file becomes a closed partition that silently misgrades the next event added.
Resolve the handler's event, read that event's row, and pair on the row's own `Can block?` cell:
- **The cell says yes** — the conflict-bearing content is whatever that row states is prevented,
quoted from the row rather than assumed. What a row prevents is not always a tool call or a
prompt; the cell, never the gate abstraction, supplies the paired content. `SubagentStop`
blocks but is subagent-scoped: its act pairs inside the subagent, under the subagent-scoping
rule above, and never against a main-session surface.
- **Non-blockable events** — `PostToolUse`, `Notification`, `SubagentStart`, `SessionStart`, and
`SessionEnd`. Nothing is prevented, so there is no act and no gate to pair; the table says so
outright for `PostToolUse` ("the tool already ran"), and this repository's own `PostToolUse`
linter records the same thing at `plugins/actionlint/hooks/actionlint-check.sh`. Treat the
message as transient feedback and pair it as nothing. Reading a `PostToolUse` linter's exit-2
stderr as a prohibition on the tool it ran *after* would manufacture an unsatisfiable conflict
against any instruction requiring that tool — the tool already ran, and the hook can neither
block nor undo it.
- **The cell says no** — nothing is prevented, so there is no act and no gate to pair; treat the
message as transient feedback and pair it as nothing. `PostToolUse` is the worked example: its
row says so outright ("the tool already ran"), and this repository's own `PostToolUse` linter
is built on that row — `plugins/actionlint/hooks/actionlint-check.sh` deliberately always
exits 0 and surfaces findings as advisory context, because an exit 2 there could block
nothing. Reading any `PostToolUse` handler's exit-2 stderr as a prohibition on the tool it ran
*after* would manufacture an unsatisfiable conflict against any instruction requiring that
tool — the tool already ran, and the hook can neither block nor undo it. Registered instead on
`PreToolUse`, whose row blocks the tool call, the same handler WOULD enter the comparison set
as the act it blocks.
- **The event has no row, or the table could not be reached** — record the surface with its event
as `blockability-unresolved` and report the pair as such, on the same terms the
`text-unresolved` rule below gives. Never infer blockability from the event's name, its prefix,
or from what a hook of that shape usually does.
- **A hook's own configuration is still not instruction text.** The command line, its arguments, and
its `matcher` are the gate, not prose addressed to the model. Extract only what is injected, under
the same no-secrets handling every settings-sourced surface gets.
Expand Down
Loading