Skip to content

fix(claude-config): correct the audit checklist's hook-form rationale and add the timeout-unit row - #2004

Merged
kyle-sexton merged 4 commits into
mainfrom
fix/claude-config-audit-checklist-hook-form
Aug 8, 2026
Merged

fix(claude-config): correct the audit checklist's hook-form rationale and add the timeout-unit row#2004
kyle-sexton merged 4 commits into
mainfrom
fix/claude-config-audit-checklist-hook-form

Conversation

@kyle-sexton

Copy link
Copy Markdown
Contributor

No linked issue

Consumer report drained from the handoff inbox: 20260729-170500-claude-config-plugin-audit-skill-drift. F1 and F2 were already fixed at HEAD; this closes the two residual findings. F3 was flagged CONTESTED by prior triage and needed adjudication against current docs, not implementation.

F3 — UPHELD, but the reporter's reason was wrong

The checklist's own rationale was that exec form "backslash-mangles ${CLAUDE_PROJECT_DIR} on native Windows." Per the hooks page fetched this session (https://code.claude.com/docs/en/hooks), Exec form and shell form:

There is no shell, so each args element is one argument exactly as written, and path placeholders like ${CLAUDE_PLUGIN_ROOT} are substituted into command and into each args element as plain strings. Special characters such as apostrophes, $, and backticks pass through verbatim because there is no shell to interpret them. No shell tokenization happens on any platform.

Mangling is a shell artifact and exec form has no shell, so that mechanism is falsified. The page also contradicts the prescription directly:

Prefer exec form for any hook that references a path placeholder. In shell form, wrap each placeholder in double quotes.

But the observation behind the finding was misdiagnosed, not invented. There is a real, documented Windows constraint, and it is narrower: "On Windows, exec form requires command to resolve to a real executable such as a .exe." This repo hit exactly that in #1006"command": "bash" resolved to the WSL relay System32\bash.exe, and because a failed hook launch is non-blocking, a destructive guard silently enforced nothing across an 82-repo session.

So the fix is to replace the wrong mechanism with the right one rather than to delete the row:

  • a new row scoped to Windows-targeting repos (bash/sh are ordinary executables elsewhere), naming both documented remedies: a real binary with the script path in args, or shell form with "shell": "bash";
  • a replacement quoting row that flags only the unquoted placeholder, never shell form itself.

That second scoping is deliberate. Quoted shell form is a spelling the page endorses for pipes, &&, redirects, and .cmd shims, and it is what this repo's own .claude/settings.json hooks use — a check that condemned shell form outright would have replaced a false negative with the mirror-image false positive.

The old mangling claim is not preserved as a caveat. Keeping the false mechanism next to the correct one would re-seed the error.

F4 — added, unit confirmed from the same page

timeout | no | Seconds before canceling. Defaults: 600 for command, http, and mcp_tool; 30 for prompt; 60 for agent.

New Category D row flags timeout > 600 as near-certainly milliseconds, and names where the confusion documentably comes from: on that same page, the Bash/PowerShell tool_input.timeout is "Optional timeout in milliseconds" with example 120000 — which read as seconds is about 33 hours. A second row covers the bare $CLAUDE_PROJECT_DIR spelling in PowerShell shell-form hooks, which the page says PowerShell "resolves to $null".

Method note carried from the report

The doc was verified twice — WebFetch and the raw .md via curl with grep — because the inbox item's own method note records the WebFetch summarizer fabricating on long docs pages. Worth knowing for the next contract-surface change: the summarizer is a lossy read of a page whose exact wording is the thing being adjudicated.

Verification

Check Result
markdownlint-cli2 (22 files) 0 issues
skill-quality check-skill.sh audit PASS — 0 errors, 2 pre-existing warnings
check-changelog-parity.sh --check-bump origin/main pass

check-orphaned-fixtures.sh was not run locally; it exceeds a 300s timeout on this machine. CI covers it.

claude-config 0.21.9 → 0.22.0 — minor, not patch: consumer-visible check behavior moves in both directions (one warning retired, four added). Three surfaces move together: reference/audit-checklist.md, context/validation-categories.md, and SKILL.md's Category D summary.

Out of scope, surfaced not fixed

  • No evals added for the new rows — audit/evals/evals.json has no hook-form coverage at all today, which is a larger gap than these two findings.
  • skills/audit/SKILL.md is 251 lines against skill-quality's 200-line soft target (pre-existing warning).
  • The audit skill has no Gotchas surface despite a documented failure history — this finding plus 0.21.9's inoperable :* check would both belong there.
  • The inbox item carries no YAML front matter, so it is invisible to the inbox's own status contract. Its triage note already flags this; still true.
  • The report's transferable method note (prefer raw .md + grep over the WebFetch summarizer for long docs pages) is not reflected in the audit skill's Phase 3 instructions.
  • docs/PLUGIN-PHILOSOPHY.md and docs/conventions/hook-config-delivery/README.md already state the exec-form rules correctly and were left alone.

Related

kyle-sexton and others added 3 commits August 8, 2026 05:53
…rt the seconds timeout unit

## F3 adjudication — the checklist row was WRONG, but so was the finding's reason

The reported finding said Category D "contradicts the hooks doc on command form". Row 90 prescribed
quoted shell form with "**no `args`**", justified by a claim the finding never engaged: that the
`"command":"bash"` + `args` exec-form variant "backslash-mangles `${CLAUDE_PROJECT_DIR}` on native
Windows". Adjudicated against https://code.claude.com/docs/en/hooks, fetched this session.

VERDICT: UPHELD — the row is wrong and now changes. But the rationale is retired on its own merits,
not by deferring to the report.

The mangling premise is false. The page's "Exec form and shell form" section states that in exec
form "path placeholders like `${CLAUDE_PLUGIN_ROOT}` are substituted into `command` and into each
`args` element as plain strings", that "Special characters such as apostrophes, `$`, and backticks
pass through verbatim because there is no shell to interpret them", and — decisively — "No shell
tokenization happens on any platform." Backslash mangling is a shell artifact; exec form has no
shell to produce it. The two propositions the triage note held open ("upstream prefers exec form"
and "this repo observed a Windows defect in it") are indeed both true, but the observed defect is
not mangling.

The real defect is narrower, is documented, and now gets its own row. The page states: "On Windows,
exec form requires `command` to resolve to a real executable such as a `.exe`." Naming `bash` as the
executable resolves to the WSL relay `System32\bash.exe` and the launch fails. That is exactly the
failure this repo hit in #1006, where a fail-open destructive guard silently enforced nothing across
an 82-repo session. The fault is in naming `bash` as the executable, not in exec form — the page's
own portable pattern is a real binary plus the script path in `args`. Row 90 generalized one true
observation about `bash` into a false rule about a whole form, and the audit skill has been telling
consumers to warn on the form the docs prefer.

Category D now tracks the page: "Prefer exec form for any hook that references a path placeholder.
In shell form, wrap each placeholder in double quotes." The new row explicitly does NOT flag shell
form where the page endorses it — pipes, `&&`, redirects, `.cmd`/`.bat` shims — so the correction
does not swing into a mirror-image false positive.

Two further rows come from the same fetch: exec-form `command` must be a real executable (the #1006
class), and a shell-form PowerShell hook must not use the bare `$CLAUDE_PROJECT_DIR` spelling, which
"PowerShell parses ... as an undefined local variable and resolves ... to `$null`".

## F4 — timeout unit

New Category D row. The hooks reference's common-fields table gives `timeout` as "Seconds before
canceling. Defaults: 600 for `command`, `http`, and `mcp_tool`; 30 for `prompt`; 60 for `agent`."
A `timeout > 600` is therefore near-certainly milliseconds. The confusion has a documented source on
the same page: the Bash and PowerShell tools' `tool_input.timeout` is "Optional timeout in
milliseconds" with example `120000` — about 33 hours if read as seconds. The row names that
neighbor so the check explains itself rather than asserting a bare threshold.

The three sibling surfaces that restated the old guidance move together: the checklist, the
Category D list in `context/validation-categories.md`, and the one-line D summary in `SKILL.md`.

Minor bump: consumer-visible check behavior changes in both directions (a warning retired, four
added).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e docs actually assert

Self-review of the previous commit found both new rows overreaching — each would have produced the
mirror image of the false positive being removed.

1. The path-placeholder row was titled "use exec form (`args`)" and rated a warning. Fixing a check
   that wrongly warned on exec form by warning on shell form instead is not a fix. The hooks page
   states a preference ("Prefer exec form for any hook that references a path placeholder") and in
   the same breath gives the correct shell-form spelling ("In shell form, wrap each placeholder in
   double quotes"), and endorses omitting `args` for pipes, `&&`, redirects, and `.cmd`/`.bat`
   shims. Quoted shell form is therefore a documented, correct spelling — and the one this
   repository's own `.claude/settings.json` hooks use, so the row as written would have warned on
   this repo. The check now flags only the unquoted placeholder and reports exec form as a
   preference rather than a finding.

2. The executable-resolution row was unscoped and rated an error, so it would have flagged
   `"command": "bash"` on macOS and Linux, where `bash` is an ordinary executable and resolves fine.
   The page scopes the constraint explicitly — "On Windows, exec form requires `command` to resolve
   to a real executable such as a `.exe`" — so the row now applies to Windows-targeting repos only.
   It also names `"shell": "bash"` as a fix alongside the `node`-plus-`args` pattern, since shell
   form is what this repo actually shipped for #1006: the page states shell form runs via "Git Bash
   on Windows", resolved by Claude Code rather than by a PATH lookup.

Same fetch as the previous commit: https://code.claude.com/docs/en/hooks. No version change — this
corrects rows added in the unreleased 0.22.0 entry, whose text is amended in place.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…quoting check

The rewritten quoting row listed only the braced placeholder spellings, which silently narrowed the
check relative to the row it replaced: the old one keyed on `$CLAUDE_PROJECT_DIR` and this repo's own
hooks are written that way (`"\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/..."`). Both spellings reach the
shell in shell form, and an unquoted path breaks on a space either way, so the check names both.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@cursor

cursor Bot commented Aug 8, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 3m 43s —— View job


Review complete

  • Gather context (diff, changed files, CLAUDE.md rules)
  • Understand the request (code-review lane)
  • Review changed files for correctness and doc-accuracy
  • Check hooks docs claims against fetched source
  • Post findings as inline comments + summary

This PR touches a contract surface (the audit skill's hook-checklist rows describing documented harness behavior), so per this repo's CLAUDE.md fresh-docs mandate I re-fetched https://code.claude.com/docs/en/hooks myself this session (both rendered and raw .md, via several targeted full-text queries) rather than trusting the PR's cited quotes.

Verified as accurate against the fetched page:

  • Exec form / shell form mechanics, "No shell tokenization happens on any platform" (reference/audit-checklist.md:91)
  • Windows exec-form executable-resolution constraint and the node+args / "shell": "bash" remedies (line 92)
  • timeout "Seconds before canceling" defaults, and the millisecond tool_input.timeout confusion source (line 88)
  • The narrowed F3 scoping (flag only unquoted placeholders, never shell form itself) matches the page's actual guidance and correctly avoids re-flagging this repo's own quoted-shell-form hooks

One finding, repeated in three places (flagged inline): the new PowerShell row claims the hooks reference says a bare $CLAUDE_PROJECT_DIR "PowerShell parses it as an undefined local variable and resolves it to $null". I could not find this sentence, or any statement about bare-$CLAUDE_PROJECT_DIR-in-PowerShell behavior, anywhere on the current page after multiple independent full-text searches (for "$null", "undefined local variable", every PowerShell-containing sentence, and every passage co-mentioning PowerShell and CLAUDE_PROJECT_DIR) — see inline comments on:

Notably this is the exact failure mode the PR's own body warns about (WebFetch summarizer fabrication on long docs pages) — worth re-verifying this one sentence specifically before merge, since the rest of the PR's doc-grounding is solid.

Not independently re-run (author-claimed, unverified by me): markdownlint-cli2, check-skill.sh audit, check-changelog-parity.sh results reported in the PR body — I did not execute these tools myself.

Everything else — the 0.21.90.22.0 minor bump rationale, the "three surfaces move together" claim (plugin.json, CHANGELOG.md, SKILL.md, validation-categories.md, audit-checklist.md are all present and mutually consistent aside from the one finding above), and the severity levels assigned to each new row — looks correct and well-aligned with CLAUDE.md's fresh-docs mandate.
· branch fix/claude-config-audit-checklist-hook-form

@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 44s —— View job


Security review complete

  • Gather context (PR diff, changed files)
  • Read changed files in full
  • Hunt for security issues (logic flaws, injection, auth gaps, secrets handling, dangerous Actions patterns)
  • Post findings

Findings

No security issues found.

This PR touches five files, and every change is prose/documentation or a version bump:

  • plugins/claude-config/.claude-plugin/plugin.json — version bump only (0.21.90.22.0), no permission or config surface change.
  • plugins/claude-config/CHANGELOG.md — changelog prose.
  • plugins/claude-config/skills/audit/SKILL.md — one-line summary of Category D's checks.
  • plugins/claude-config/skills/audit/context/validation-categories.md — Category D checklist prose.
  • plugins/claude-config/skills/audit/reference/audit-checklist.md — Category D table rows with rationale text.

None of these introduce or modify a GitHub Actions workflow, a hook script, executable code, .mcp.json/settings.json entries, or any secret/credential handling. The content is advisory guidance consumed by the audit skill's reasoning (an AI-read checklist), not code that is parsed or executed by the harness — so there's no injection, authorization, or trust-boundary surface here to evaluate. The PR's substance is a factual correction (replacing a false "exec form mangles ${CLAUDE_PROJECT_DIR} on Windows" claim with the documented "exec form requires a real executable" constraint) plus a new units-check row, both cited against the fetched hooks reference page.

Nothing in this diff falls into this lane's scope (logic flaws, injection surfaces, credential handling, or dangerous Actions patterns) — deferring to zizmor's advisory lane doesn't apply either, since there's no workflow or dependency-pin change to statically check.

Branch: fix/claude-config-audit-checklist-hook-form

Comment thread plugins/claude-config/skills/audit/reference/audit-checklist.md Outdated
Comment thread plugins/claude-config/skills/audit/context/validation-categories.md Outdated
Comment thread plugins/claude-config/CHANGELOG.md Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 105e7801b6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/claude-config/skills/audit/reference/audit-checklist.md Outdated
@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown

Claude has reviewed this PR 2 times. The lane skips further automatic reviews after 5; deleting this comment resets the count.

…ing large timeouts

Two review findings, both of which would have shipped audit rows a consumer
could not trace to a documented rule — the same defect class 0.21.9 removed.

The PowerShell bare-`$CLAUDE_PROJECT_DIR` row carried a quote attributed to the
hooks page that is not on it. Re-fetched and searched today: the page's only
placeholder-quoting guidance is the generic "In shell form, wrap each
placeholder in double quotes", and it says nothing about PowerShell resolving an
undefined variable. The claim also depends on whether the harness substitutes
the bare `$NAME` spelling before PowerShell ever parses it, which the page does
not document either — so the row rested on two unverifiable premises at once.
Dropped rather than re-sourced; the generic quoting row already carries the safe
advice, and a row that emits untraceable findings is worse than no row.

The timeout row keyed on `> 600`, but the page documents defaults, not a
maximum. A deliberately long-running hook may legitimately exceed 600, so that
rule would manufacture findings against correct configuration. It now flags a
recognizably millisecond-scale value — a round thousands multiple like `30000`
or `120000` — and asks for corroboration from the hook's expected runtime when a
value is large but not millisecond-shaped. The supporting claim about
`tool_input.timeout` being "documented in milliseconds" was also over-stated:
the page shows the example value `120000` but never states that field's unit in
prose, so the checklist no longer says it does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 2m 39s —— View job


Review complete

  • Gather context (PR diff since last review, CLAUDE.md rules)
  • Understand the request (code-review lane, re-review after synchronize)
  • Check whether prior findings (unsourceable PowerShell quote, over-flagging large timeouts) were addressed
  • Re-fetch https://code.claude.com/docs/en/hooks fresh this session and re-verify every doc claim still present in the diff
  • Post final findings

This trigger fired on the latest push (13f638e, "drop an unsourceable checklist row and stop flagging large timeouts"), which lands on top of the three prior review rounds on this PR. I re-fetched the hooks reference myself this session (per CLAUDE.md's fresh-docs mandate — this diff is a contract-surface change) rather than trusting any earlier summary, including my own from the previous round.

Both previously-flagged issues are resolved at HEAD:

  1. Unsourceable PowerShell $null claim (flagged 3× in the prior round, on audit-checklist.md, validation-categories.md, and CHANGELOG.md). The row is gone from all three files. I re-searched the live hooks page myself this session (multiple targeted full-text queries for PowerShell + $CLAUDE_PROJECT_DIR, $null, "undefined local variable") and confirm: no such sentence exists on the page. CHANGELOG.md:31-40 now documents the row was drafted and dropped for exactly this reason — an honest account of the failure, not a silent removal.

  2. Codex's P2 finding (over-flagging legitimately long timeouts as millisecond mistakes). audit-checklist.md:88 now scopes the check to "a recognizably millisecond-scale value — a round thousands multiple such as 30000 or 120000" and explicitly says "Do NOT flag merely-large values: the page documents defaults, not a maximum." validation-categories.md:42-44 carries the same scoping. A timeout: 900 for a legitimately slow async hook no longer trips this rule.

Fresh-verified against the live page today, all accurate:

  • timeout: "Seconds before canceling. Defaults: 600 for command, http, and mcp_tool; 30 for prompt; 60 for agent" — exact match.
  • Exec form: "No shell tokenization happens on any platform" and placeholder substitution as plain strings — exact match.
  • Windows exec-form constraint: "requires command to resolve to a real executable such as a .exe" — exact match.
  • The "shell": "bash" remedy in audit-checklist.md:92 — the page states the shell field "Accepts bash or powershell. Defaults to bash, or to powershell on Windows when Git Bash isn't installed," and separately that the command "is passed to a shell: sh -c on macOS and Linux, Git Bash on Windows, or PowerShell when Git Bash isn't installed." This supports the row's "routes through Git Bash instead of a PATH lookup" characterization — a defensible reading of documented behavior, not a fabrication.
  • CHANGELOG.md's claim that the page "does not state that field's unit in prose" for the Bash/PowerShell tool's own tool_input.timeout (only inferable from the 120000 example) — confirmed by my own search: no prose unit statement exists for that field.

No new findings. Three surfaces (audit-checklist.md, validation-categories.md, CHANGELOG.md) stay consistent with each other and with the live docs. SKILL.md's one-line Category D summary and the plugin.json 0.21.90.22.0 bump are unchanged from the prior round and remain correct.

Not independently re-run (author-claimed, unverified by me): markdownlint-cli2, check-skill.sh audit, check-changelog-parity.sh results.
· branch fix/claude-config-audit-checklist-hook-form

@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 58s —— View job


Security review complete

  • Gather context (PR diff via gh pr diff 2004, changed files at HEAD 13f638e)
  • Read changed files in full
  • Hunt for security issues (logic flaws, injection, auth gaps, secrets handling, dangerous Actions patterns)
  • Post findings

Findings

No security issues found.

This PR touches five files, and every change is prose/documentation or a version bump — confirmed against gh pr diff 2004 at the current HEAD (13f638e), which supersedes the two prior review passes on this PR:

None of these introduce or modify a GitHub Actions workflow, a hook script, executable code, .mcp.json/settings.json entries, or secret/credential handling. The content is advisory guidance consumed by the audit skill's reasoning (an AI-read checklist), not code that is parsed or executed by the harness — so there's no injection, authorization, or trust-boundary surface here to evaluate.

The two latest commits (aa81749105e78013f638e) narrow the checklist rows further (dropping the unsourceable PowerShell row entirely and replacing the flat timeout > 600 flag with a millisecond-shape heuristic) — same conclusion applies: no executable or config surface changed, only the wording/scope of guidance text.

Deferring to zizmor's advisory lane doesn't apply either, since there's no workflow or dependency-pin change in this diff.
· Branch: fix/claude-config-audit-checklist-hook-form

@kyle-sexton
kyle-sexton merged commit 70e3b32 into main Aug 8, 2026
33 checks passed
@kyle-sexton
kyle-sexton deleted the fix/claude-config-audit-checklist-hook-form branch August 8, 2026 11:18
kyle-sexton added a commit that referenced this pull request Aug 8, 2026
Main released its own 0.22.0 (#2004) while this branch was in flight; the
unhobble release moves to 0.23.0 with both changelog sections preserved.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant