Skip to content

Guards deny an event that lacks the field they read (#186, first half) - #187

Open
pedrohcgs wants to merge 1 commit into
mainfrom
feat/guard-payload-contract
Open

pedrohcgs wants to merge 1 commit into
mainfrom
feat/guard-payload-contract

Conversation

@pedrohcgs

Copy link
Copy Markdown
Owner

Summary

git-guardrails.py and root-of-trust-guard.py read tool_input.command. When that field was missing they treated the command as empty and allowed it. A Claude Code update that renamed the field would therefore have switched both guards off, and nobody would have seen it. This is the half of #186 the owner chose to ship: no run log, no session-start check, no settings change.

  • Denied now: a well-formed event with no tool_name, or a Bash event with no string tool_input.command. The reason names Hook health: nothing notices a wired hook that runs but no longer does anything (port clo-author 26.09's hook log and SessionStart check?) #186 and the way out ("disableAllHooks": true in .claude/settings.local.json until the hooks are updated).
  • How it refuses: with a deny decision, not exit 1, so it works on Claude Code versions that don't honour onFailure.
  • Decisions, recorded in each docstring:
    • unparseable input still fails open (pinned by a3 and g22);
    • a Write/Edit event with no file_path still fails open, because that check is advisory;
    • root-of-trust-guard keeps its own copy of the check, so a stubbed or broken sibling cannot remove it;
    • issue-guard is unchanged: it is not a security boundary, and Claude Code starts it only for gh commands.
  • Release: CHANGELOG v2.9.1 (fix-only, untagged); the guide and CITATION are at 2.9.1. TROUBLESHOOTING names the new refusal as a third cause.

Verification

  • Battery: twelve new cases, a123–a129 and b39–b43, controls included; 334 cases in all, ALL PASS.

  • Mutants: five, run through HOOK_DIR, and each fails the cases aimed at it:

    Mutant Cases that fail
    check removed from root-of-trust-guard a123–a126, a129
    check removed from git-guardrails b39–b41
    Write/Edit made to block b42–b43
    non-Bash events required to carry command a127
    empty command refused a128

    Recorded in the LEDGER, with the guard rows' N and FPR updated (78/78 with 0/51; 80/80 with 0/31).

  • Live: every Bash call made while writing this change ran through the edited guards in the working tree, and none was refused. The field names match the PreToolUse input in the hooks docs (read 2026-11-06).

  • ./scripts/backtest.sh passes, with 245 portability cases.

…ield they read

#186, the half the owner chose to ship. Both guards read tool_input.command and, when it was
missing, treated the command as empty and allowed it, so a Claude Code update that renamed the
field would have switched them off silently. They now deny a well-formed event with no
tool_name, or a Bash event with no string tool_input.command, with a reason that names #186
and the way out. It is a deny decision, so it holds on Claude Code versions without onFailure.

Decisions recorded in each docstring: unparseable input still fails open (a3, g22); a
Write/Edit event with no file_path still fails open, since that check is advisory;
root-of-trust-guard keeps its own copy of the check so a stubbed sibling cannot remove it;
issue-guard is unchanged (not a security boundary, and only spawned for gh commands).

Twelve battery cases (a123-a129, b39-b43), 334 in all; five mutants run through HOOK_DIR each
fail the cases aimed at them (LEDGER). Field names checked against the hooks docs; every Bash
call of this session ran through the edited guards with no refusal. TROUBLESHOOTING names the
new refusal as a third cause. CHANGELOG v2.9.1 (untagged), guide and CITATION at 2.9.1,
guide re-rendered and restamped.

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 7, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-07T04:06:18.588047Z 2b23f8e PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@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: 2b23f8eb08

ℹ️ 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 TROUBLESHOOTING.md

- **`can't open file …/.claude/hooks/<name>.py`: the hook script is missing.** Usually a branch switch or an old checkout removed `.claude/hooks/` while the session still holds the settings that point there. Claude cannot repair this from inside the session, because the repair is itself a blocked call. From a terminal, run `git checkout -- .claude/hooks` (or switch back with `git switch -`), then start a new session. Tracked in [#180](https://github.com/pedrohcgs/claude-code-my-workflow/issues/180).
- **`python3: command not found`, or a Microsoft Store message: no working `python3`.** On Windows, the python.org installer provides `python` and `py` but not `python3`, and the `python3` that ships with Windows opens the Store. Make `python3 --version` work in Git Bash, for example with the Microsoft Store's Python, which provides `python3`. Tracked in [#179](https://github.com/pedrohcgs/claude-code-my-workflow/issues/179).
- **`Claude Code sent … so this guard cannot read the command` (only Bash is refused): the hook payload changed.** Since v2.9.1, `git-guardrails` and `root-of-trust-guard` refuse an event that lacks the field they read, rather than read it as an empty command and allow it. Seeing this means a Claude Code update renamed or dropped that field. Look for an update to the template's `.claude/hooks/`. Until then, run the command in a terminal, or switch the project's hooks off with `"disableAllHooks": true` in `.claude/settings.local.json`, and turn them back on once the hooks are updated. Tracked in [#186](https://github.com/pedrohcgs/claude-code-my-workflow/issues/186).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Distinguish which missing field blocks edit tools

The parenthetical is true only when tool_input.command is missing. If the payload change removes or renames tool_name, git-guardrails.py rejects the event before checking its tool type, and its matcher also covers Write, Edit, and MultiEdit, so all of those tools are refused as well. In that failure mode this troubleshooting entry misstates the scope of the lockout; distinguish the missing-command case from the missing-tool-name case.

Useful? React with 👍 / 👎.

This branch has not been deployed

No deployments
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.

2 participants