fix(hooks): add jq type-confusion matrix test, close two origin-hook gaps - #1314
Conversation
…gaps Adds hooks/test_gitapex_jq_type_confusion_matrix.py: a shared, parametrized test running the full jq falsy/type-confusion value matrix (absent, null, false, true, 0, "", array, object) against every `(.field == null) or (.field | type == "X")`-shaped guard clause across the six hooks issue #1218 names, per #1237's own retrospective proposal for PR #1213. Re-verifying issue #1312's own drafted ACM against the real hooks found its Proof method ("passes now" against current main) did not hold: two of the six named hooks, check-pr-issue-acm-disclosure.sh and check-pr-title-convention.sh, still lack a tool_name-type guard entirely and still carry the pre-PR#1213 tool_input-shape predicate that fails to reject `tool_input: false` -- live-confirmed, and independently tracked as issues #1216/#1217. Both are fixed here with the exact predicate PR #1213 already proved correct, so the new matrix test genuinely passes across all six hooks rather than documenting a live gap. .gitapex/ssot.json's rule text for both hooks' gates is updated to disclose the new guards, matching the four already-fixed hooks' own convention. Closes #1312, Closes #1216, Closes #1217
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1314 +/- ##
=======================================
Coverage 99.51% 99.51%
=======================================
Files 117 118 +1
Lines 20839 20936 +97
Branches 2455 2460 +5
=======================================
+ Hits 20738 20835 +97
Misses 101 101 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
hooks/test_gitapex_jq_type_confusion_matrix.py carried two lines unreachable by its own current guarded-field registry: an unused __repr__ (dead once explicit pytest ids= are passed) and a branch in bad_shape_phrase that only fires for a string-type guard's null/absent case denying with exit 2, which none of the six registered hooks ever do. Removes the dead __repr__ and collapses the branch into a non-branching bad_shape_phrases helper checked without regard to guard type, restoring 100% coverage on the new file instead of adding an unreachable branch to chase the coverage gate. Refs #1312, Refs #1216, Refs #1217
Adversarial convention-adherence review of PR #1314's own diff (this skill's step 8 inner layer) found three real divergences from the four already-fixed sibling hooks' own established pattern: - Both new tool_name-guard comments said "instead of failing closed" where all four siblings say "rather than failing closed" verbatim. - check-pr-title-convention.sh's freshly-added tool_input-guard comment skipped the general non-object-crash framing paragraph every sibling that introduces this predicate fresh includes before layering in the false-specific refinement. - test_denied_when_tool_name_is_not_a_string in both hook test files hand-rolled its own subprocess call instead of widening the file's own run() helper's tool_name type to object and reusing it, unlike three of four sibling test files that do exactly that. Also independently re-verified (against the actual sibling test files, not just the review's own claim) that the sibling tool_input-shape tests hand-roll rather than reuse run() -- that shape is the established convention there, not a gap, so the two hooks' own equivalent tests are left hand-rolled with a comment explaining why. Refs #1312, Refs #1216, Refs #1217
Two-layer independent review (drafting-a-pr-to-merge Step 8)Recorded against head Outer layer (GitHub-native reviewer): No Claude Code Review App confirmed installed on this repository. Requested a GitHub Copilot review ( Inner layer (always-runs adversarial fan-out): four category-focused passes dispatched against the diff, each with independent verification before anything below is reported as a finding.
All inner-layer findings are either fixed (convention-adherence, 3/3), filed separately with justification for not expanding this PR's scope (regression, 1 finding -> issue #1315), or considered and correctly left as-is against the closing issues' own explicit constraints (reuse, 2 points). No outstanding blocking finding against this diff. Generated by Claude Code |
Summary
Adds
hooks/test_gitapex_jq_type_confusion_matrix.py, a shared parametrized test running the full jq falsy/type-confusion value matrix against every(.field == null) or (.field | type == "X")-shaped guard clause across the six hooks issue #1218 names, per #1237's own retrospective proposal. Re-verifying #1312's own drafted Acceptance Criteria Map against the real hooks found two of those six (check-pr-issue-acm-disclosure.sh,check-pr-title-convention.sh) still fail closed on only 6 of the 8 matrix values -- independently tracked as #1216/#1217 -- so this PR also applies both issues' own already-specified, already-proven fix in the same change, rather than shipping a matrix test that documents a live gap instead of closing it.Facts
(.field == null) or (.field | type == "X")guard inhooks/*.sh, run in CI" as the systemic gate that would have caught PR fix(hooks): fail closed on missing/malformed jq in four PreToolUse gates #1213's four separate adversarial-review repairs in one pass. Issue hooks/*.sh: add CI-enforced jq falsy/type-confusion matrix test for guard clauses #1312 requests exactly that.planning-a-branch-from-an-issue's Step 5) against the live hook source, rather than trusting the draft: Row 1's Proof method requires the new test to "confirm it passes now" against currentmain. Live reproduction this session found that untrue for two of the six hooks Row 1 itself names:tool_name: 0andtool_name: ["mcp__github__create_pull_request"]both silently fall through as "not our tool" (exit 0) on bothcheck-pr-issue-acm-disclosure.shandcheck-pr-title-convention.sh-- neither hook has atool_name-type guard at all.tool_input: falsecrashes both hooks with jq's own exit 5 ("Cannot index/check boolean ..."), pastdeny(), non-blocking per Claude Code's own PreToolUse contract -- both hooks still carry the pre-PR#1213 predicate(.tool_input // {}) | type == "object", which treats JSONfalsethe same asnull.tool_input: false) and hooks/: check-pr-issue-acm-disclosure.sh and check-pr-title-convention.sh fail open on a non-string tool_name (same class as #1208) #1217 (non-stringtool_name), filed 2026-08-18, each already carrying a complete ACM proposing the identical fix PR fix(hooks): fail closed on missing/malformed jq in four PreToolUse gates #1213 used on the other four hooks..gitapex/ssot.json'spr-issue-acm-disclosureandpr-title-conventiongate entries had noruletext disclosing any fail-closed jq guard, unlike the four already-fixed hooks' own entries -- updated to match, per PR fix(hooks): fail closed on missing/malformed jq in four PreToolUse gates #1213's own retrospective precedent (repair 9) of keeping registeredruletext current with the guards a hook actually enforces.Assumptions
Skill audit evidence
evaluating-deterministic-gate-qualityrun (isolated subagent dispatch, no CLAUDE.md in context) against bothhooks/check-pr-issue-acm-disclosure.shandhooks/check-pr-title-convention.sh, read againstskills/evaluating-deterministic-gate-quality/references/dimensions.mdincluding dimension 15 (fail-closed default on malformed input). Verdict: both hooks well-formed and well-placed. The review live-tested the actual guard behavior (not just read the diff) against synthetic payloads on both the fixed and pre-fix script bodies: pre-fix,tool_name:0/array exited 0 (silent allow) andtool_input:falsecrashed with jq's own exit 5 on both hooks; post-fix, both cases correctly deny (exit 2) and the valid-call happy path still exits 0/2 as expected. One named minor gap (dimension 12): the(.field == null) or (.field | type == "X")predicate is hand-duplicated across six hook files, mitigated (not eliminated) by the new sharedhooks/test_gitapex_jq_type_confusion_matrix.pyregression test rather than a single-source predicate -- issue hooks/: extract the guard prologue into a shared sourced helper (duplication caused #1216 and #1217) #1218 tracks the eventual shared-helper consolidation.test_denied_when_tool_name_is_not_a_string(added to both hooks' own test files) and thefalsecase added totest_denied_when_tool_input_is_not_an_object(and the newhooks/test_gitapex_jq_type_confusion_matrix.py) were each written and confirmed failing against the pre-fix hook bodies before the fix landed (TDD red: 12 failures), specifically to defeat the old guards rather than exercise their happy path -- not merely happy-path coverage.Acceptance Criteria Map
guard clause inhooks/*.sh` is covered by the full 8-value matrix, run in CItool_name-type andtool_input-shape guards across all six hooks issue #1218 names, plus the.tool_input.command/.tool_input.file_pathleaf guardshooks/test_gitapex_jq_type_confusion_matrix.py(a shared canonical value matrix + per-hook guarded-field registry, run via subprocess against each real hook script); no new CI job needed --hooksis already onpyproject.toml'stestpathsuv run --frozen pytest hooks/test_gitapex_jq_type_confusion_matrix.py -q: TDD red confirmed first (12 failures, all against the two origin hooks'tool_name/tool_inputguards) before the fix below; green after the fix (96 passed)check-pr-issue-acm-disclosure.sh,check-pr-title-convention.sh) must actually fail closed across the full matrix, not just today's already-fixed fourbeforetool_nameextraction,(.tool_input == null) or (.tool_inputreplacing the falsy-gap-prone(.tool_input // {}) | type == "object"`.shfiles; added matchingpytest.mark.parametrizeregression cases to each hook's own existingtest_gitapex_check_*.pyfile (closes #1216, #1217 per those issues' own stated Acceptance Criteria); refreshed both hooks'.gitapex/ssot.jsonruletextRisk / blast radius
check-pr-issue-acm-disclosure.shandcheck-pr-title-convention.shgate everymcp__github__create_pull_request/update_pull_requestcall; a regression here would block PR creation/updates repository-wide. Covered by the full existing per-hook test suites (both still 100% passing) plus the new matrix test.tests/test_gitapex_scan_harden_checkout_pin_drift.py::test_repository_workflows_are_drift_free, caused by this container's own shallow git clone (git rev-parse --is-shallow-repositoryreturns true; the gate correctly refuses to resolve a shallow-boundary commit as authoritative). Real CI checks out withfetch-depth: '0'(full history) per.github/actions/harden-checkout, so this does not reproduce there. Not touched by this diff.Rollback
Revert this commit; both hook
.shfiles and their test suites return to the pre-PR-#1213-parity state (four hooks fixed, two not), and the new matrix test file is removed. No schema/data migration involved.Verification
uv run --frozen pytest hooks/test_gitapex_jq_type_confusion_matrix.py hooks/test_gitapex_check_pr_issue_acm_disclosure_shell.py hooks/test_gitapex_check_pr_title_convention.py -q --no-cov-> 143 passeduv run --frozen pytest hooks/ -q --no-cov-> 383 passeduv run --frozen pytest --cov-report=xml --cov-report=json(full suite, mirrors CI'stest.yml) -> 5619 passed, 1 failed (pre-existing, environment-caused shallow-clone issue documented above, unrelated to this diff)uv run --frozen python3 .github/scripts/gitapex_scan_ssot_schema.py-> "No ssot.json drift found."bash -n hooks/check-pr-issue-acm-disclosure.sh && bash -n hooks/check-pr-title-convention.sh-> syntax OKuv run --frozen ruff check/ruff format --check/mypy/betterleaks(pre-commit hooks) -> all passed on commitChecklist
.gitapex/ssot.json's ownruletext, which is updated.gitapex/ssot.json,hooks/check-pr-issue-acm-disclosure.sh,hooks/check-pr-title-convention.sh); see the## Skill audit evidencesection aboveevals/*/split.mdKept-edit-log entry addedSKILL.mdStop-boundary bullets or dispatch branches addedRelated Issue
Closes #1312, Closes #1216, Closes #1217