From 12d024f5c10b5697c7b99117b7b8bf2e90c345f2 Mon Sep 17 00:00:00 2001 From: Avi Fenesh Date: Thu, 23 Apr 2026 12:34:16 +0300 Subject: [PATCH 1/2] fix(claude-code): catch up to v2.1.118 (mcp_tool hook type) Closes #764. Triage of Claude Code v2.1.117 -> v2.1.118. The release added `type: "mcp_tool"` so hooks can invoke MCP tools directly. Without this fix, agnix would false-positive CC-HK-016 (unknown hook type) on every v2.1.118+ user following the release notes. Added `"mcp_tool"` to BOTH allow-lists in crates/agnix-core/src/rules/hooks/helpers.rs: - `valid_types` (lines 607, 973): used by CC-HK-016 strict check - `known_non_command` (lines 532, 974): used to flag async on non-command hook types Plus a regression test (`test_cc_hk_016_mcp_tool_type_valid`) asserting `mcp_tool` doesn't trigger CC-HK-016. Doc gap caveat: as of 2026-04-23 the docs at code.claude.com/docs/en/hooks only list 4 types (command, prompt, agent, http). The v2.1.118 release notes explicitly mention mcp_tool and are authoritative until the docs catch up. Other v2.1.118 changes need no agnix change: - Vim visual mode, /cost+/stats -> /usage, custom themes (UI/CLI) - Plugin themes/ directory: agnix's plugin.rs has no strict fields check, no false positive - DISABLE_UPDATES env var: not a config file - wslInheritsWindowsSettings policy key: settings.json field; agnix doesn't validate settings.json fields directly - autoMode.* '\$defaults' token: settings.json field - claude plugin tag (CLI), OAuth/credential bug fixes (runtime) Documentation: - Bumped verified_on to 2026-04-23 for all 25 CC-HK-* rules in knowledge-base/rules.json + synced crates/agnix-rules/rules.json - Regenerated website/docs/rules/generated/*.md Verified locally: - cargo clippy --workspace --all-targets --all-features -- -D warnings clean - cargo test -p agnix-core --lib: passes (+1 new regression test) --- CHANGELOG.md | 1 + crates/agnix-core/src/rules/hooks/helpers.rs | 8 ++-- crates/agnix-core/src/rules/hooks/tests.rs | 27 +++++++++++ crates/agnix-rules/rules.json | 50 ++++++++++---------- knowledge-base/rules.json | 50 ++++++++++---------- website/docs/rules/generated/cc-hk-001.md | 2 +- website/docs/rules/generated/cc-hk-002.md | 2 +- website/docs/rules/generated/cc-hk-003.md | 2 +- website/docs/rules/generated/cc-hk-004.md | 2 +- website/docs/rules/generated/cc-hk-005.md | 2 +- website/docs/rules/generated/cc-hk-006.md | 2 +- website/docs/rules/generated/cc-hk-007.md | 2 +- website/docs/rules/generated/cc-hk-008.md | 2 +- website/docs/rules/generated/cc-hk-009.md | 2 +- website/docs/rules/generated/cc-hk-010.md | 2 +- website/docs/rules/generated/cc-hk-011.md | 2 +- website/docs/rules/generated/cc-hk-012.md | 2 +- website/docs/rules/generated/cc-hk-013.md | 2 +- website/docs/rules/generated/cc-hk-014.md | 2 +- website/docs/rules/generated/cc-hk-015.md | 2 +- website/docs/rules/generated/cc-hk-016.md | 2 +- website/docs/rules/generated/cc-hk-017.md | 2 +- website/docs/rules/generated/cc-hk-018.md | 2 +- website/docs/rules/generated/cc-hk-019.md | 2 +- website/docs/rules/generated/cc-hk-020.md | 2 +- website/docs/rules/generated/cc-hk-021.md | 2 +- website/docs/rules/generated/cc-hk-022.md | 2 +- website/docs/rules/generated/cc-hk-023.md | 2 +- website/docs/rules/generated/cc-hk-024.md | 2 +- website/docs/rules/generated/cc-hk-025.md | 2 +- 30 files changed, 107 insertions(+), 79 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5622f0be..0c0952efe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Kiro CLI 1.28.3 -> 2.0.1 doc refresh** (#751) - re-verified all 51 Kiro rules (KIRO-* steering, KR-AG-* agents, KR-HK-* hooks, KR-MCP-* MCP, KR-PW-* powers, KR-SK-* skills) against the upstream `https://kiro.dev/changelog/cli/` HTML changelog and the relevant pages under `https://kiro.dev/docs/`. The 1.28 -> 2.0 jump was platform expansion (Windows native, headless mode `KIRO_API_KEY`, TUI graduation, granular tool trust, session settings tool, simplified agent creation) - none of those add fields to any of the 5 Kiro file types agnix validates. Two ambiguous changelog mentions (`availableAgents`/`trustedAgents` from v1.25, `knowledgeIndex` resource type from v1.23/v1.24) were verified against `kiro.dev/docs/cli/custom-agents/configuration-reference` and confirmed NOT to appear in `.kiro/agents/*.json` field lists - they're either global settings or were never shipped as documented config. No code changes required. Bumped `verified_on` for all 51 Kiro rules to 2026-04-22; bumped `RESEARCH-TRACKING.md` "Last Reviewed" for Kiro CLI from 2026-02-05 to 2026-04-22 (also added `.kiro/powers/*/POWER.md` to the Config Format column - it was missing). Discovered: agnix has FIVE Kiro validators, not four - `kiro_power.rs` (KR-PW-001..008) for `.kiro/powers/*/POWER.md` was undocumented in earlier triage briefings. ### Fixed +- **Claude Code v2.1.117 -> v2.1.118 catch-up** (#764) - upstream v2.1.118 added `type: "mcp_tool"` so hooks can invoke MCP tools directly. Without this fix, agnix would false-positive **CC-HK-016** (unknown hook type) on every v2.1.118+ user that follows the release notes. Added `"mcp_tool"` to both `valid_types` arrays AND `known_non_command` arrays in `crates/agnix-core/src/rules/hooks/helpers.rs` (4 sites total — two for the strict-fields pass, two for the async-on-non-command pass) plus a regression test asserting `mcp_tool` doesn't trigger CC-HK-016. Note: as of 2026-04-23 the docs at `code.claude.com/docs/en/hooks` only list 4 types; the v2.1.118 release notes are authoritative for the new `mcp_tool` type until the docs catch up. Bumped `verified_on` for all 25 CC-HK-* rules to 2026-04-23. Other v2.1.118 changes (vim visual mode, `/cost`+`/stats`->`/usage`, custom themes, plugin `themes/` directory, `DISABLE_UPDATES`, `wslInheritsWindowsSettings` policy key, `autoMode.*` `"$defaults"` token, `claude plugin tag`, OAuth and credential bug fixes) need no agnix changes — they're runtime/UI/CLI behavior or settings.json fields agnix doesn't validate directly. - **GitHub Copilot v0.42.2 -> v0.43.0 catch-up** (#748) - upstream PR `microsoft/vscode-copilot-chat#4964` (in v0.43.0) makes the `description` frontmatter field on `.instructions.md` files a user-visible feature in the VS Code Chat Customizations UI. Without this fix, agnix would false-positive **COP-004** (unknown frontmatter key) and offer an auto-fix to delete the line on every v0.43.0+ user that follows the docs. Added `"description"` to `KNOWN_KEYS` and a typed `description: Option` field on `CopilotScopedSchema` in `crates/agnix-core/src/schemas/copilot.rs` plus two regression tests (parse + unknown-key check). Documentation: bumped `verified_on` for all 25 COP-* rules to 2026-04-22; bumped `RESEARCH-TRACKING.md` "Last Reviewed" for GitHub Copilot from 2026-02-05 to 2026-04-22. Verified other v0.43.0 changes (hooks/plugins UI wiring, AGENTS.md/CLAUDE.md multi-root discovery fix, internal NES/session/telemetry refactors) need no agnix changes - they're UI/runtime only with no schema impact. - **OpenCode v1.3.13 -> v1.14.20 catch-up** (#752) - upstream PR `anomalyco/opencode#13748` (in v1.3.16) added the `tui.mouse: bool` config key to disable terminal mouse capture. Without this fix, agnix would false-positive **OC-TUI-001** (unknown TUI key) on any current OpenCode config that includes `{"tui": {"mouse": false}}`. Added `"mouse"` to `KNOWN_TUI_KEYS` in `crates/agnix-core/src/schemas/opencode.rs` plus two regression tests (`mouse: false` and `mouse: true`). Documentation: bumped `verified_on` for all 46 OC-* rules to 2026-04-22; bumped `RESEARCH-TRACKING.md` "Last Reviewed" for OpenCode from 2026-02-05 to 2026-04-22. Verified other schema additions in the bump window (MCP `oauth_redirect_uri` in v1.4.3, `compaction.autocontinue` in v1.4.4, `compaction.preserve_recent_tokens` in v1.14.19, LLM Gateway provider in v1.4.9) need no agnix changes - they extend optional sub-fields agnix doesn't enforce. Note: as of v1.14.x, upstream `config.ts` deprecates the in-config `tui` block in favour of a separate `tui.json` file; agnix's TUI validator continues to help users on v1.3-v1.4 and users who keep the deprecated form. Migrating to validate `tui.json` is a future enhancement, not a blocker for this triage. - **Codex CLI rust-v0.118.0 -> rust-v0.122.0 catch-up** (#747) - upstream `config-schema.json` (verified 2026-04-22) gained 8 new top-level keys (`experimental_realtime_start_instructions`, `experimental_realtime_ws_startup_context`, `include_apps_instructions`, `include_environment_context`, `include_permissions_instructions`, `marketplaces`, `realtime`, `tool_suggest`). Without this fix, agnix would false-positive **CDX-004** (unknown TOML top-level key, primary path for `.codex/config.toml`) and CDX-CFG-006 (its JSON/YAML equivalent) on any current Codex config that uses them. Added all 8 to `KNOWN_TOP_LEVEL_KEYS` in `crates/agnix-core/src/schemas/codex.rs` (the TOML path used by CDX-004) AND `KNOWN_CONFIG_TOP_LEVEL_KEYS` in `crates/agnix-core/src/rules/codex.rs` (the JSON/YAML path). Also added `realtime` and `marketplaces` to `KNOWN_TABLE_KEYS` since both can appear as TOML tables (`[realtime]`, `[[marketplaces]]`). Plus a regression test that runs an inline-table config and a section-table config and asserts zero CDX-004 diagnostics. Documentation: bumped `verified_on` for all 58 CDX-* rules to 2026-04-22; bumped `RESEARCH-TRACKING.md` "Last Reviewed" for Codex CLI from 2026-02-05 to 2026-04-22. Verified MCP server config (`env`, `env_vars`), AGENTS.md discovery refactor (#18035), filesystem deny-read globs (#15979), and PermissionRequest hooks (#17563) require no agnix changes - their upstream effects are runtime-only, additive, or already covered by existing validators. diff --git a/crates/agnix-core/src/rules/hooks/helpers.rs b/crates/agnix-core/src/rules/hooks/helpers.rs index 295f471b0..c6cde020c 100644 --- a/crates/agnix-core/src/rules/hooks/helpers.rs +++ b/crates/agnix-core/src/rules/hooks/helpers.rs @@ -529,7 +529,7 @@ pub(super) fn validate_cc_hk_013_async_field( content: &str, diagnostics: &mut Vec, ) { - let known_non_command = ["prompt", "agent", "http"]; + let known_non_command = ["prompt", "agent", "http", "mcp_tool"]; for_each_raw_hook(raw_value, |event, matcher_idx, hook_idx, hook| { if hook.get("async").is_some() { if let Some(hook_type) = hook.get("type").and_then(|t| t.as_str()) { @@ -604,7 +604,7 @@ pub(super) fn validate_cc_hk_016_unknown_type( content: &str, diagnostics: &mut Vec, ) { - let valid_types = ["command", "prompt", "agent", "http"]; + let valid_types = ["command", "prompt", "agent", "http", "mcp_tool"]; for_each_raw_hook(raw_value, |event, matcher_idx, hook_idx, hook| { if let Some(type_value) = hook.get("type") { let hook_type_str; @@ -970,8 +970,8 @@ pub(super) fn validate_all_raw_hooks( let check_024 = config.is_rule_enabled("CC-HK-024"); let check_025 = config.is_rule_enabled("CC-HK-025"); - let valid_types = ["command", "prompt", "agent", "http"]; - let known_non_command = ["prompt", "agent", "http"]; + let valid_types = ["command", "prompt", "agent", "http", "mcp_tool"]; + let known_non_command = ["prompt", "agent", "http", "mcp_tool"]; let valid_shells = ["bash", "powershell"]; let tool_events = HooksSchema::TOOL_EVENTS; diff --git a/crates/agnix-core/src/rules/hooks/tests.rs b/crates/agnix-core/src/rules/hooks/tests.rs index b812f4190..45c52017e 100644 --- a/crates/agnix-core/src/rules/hooks/tests.rs +++ b/crates/agnix-core/src/rules/hooks/tests.rs @@ -2950,6 +2950,33 @@ fn test_cc_hk_016_agent_type_valid() { assert_eq!(cc_hk_016.len(), 0); } +#[test] +fn test_cc_hk_016_mcp_tool_type_valid() { + // Claude Code v2.1.118 added `type: "mcp_tool"` so hooks can invoke + // MCP tools directly. Without this fix, agnix would false-positive + // CC-HK-016 (unknown hook type) on every v2.1.118+ user that follows + // the docs. + let content = r#"{ + "hooks": { + "PostToolUse": [ + { + "hooks": [ + { "type": "mcp_tool", "tool": "mcp__server__do_thing" } + ] + } + ] + } + }"#; + + let diagnostics = validate(content); + let cc_hk_016: Vec<_> = diagnostics + .iter() + .filter(|d| d.rule == "CC-HK-016") + .collect(); + + assert_eq!(cc_hk_016.len(), 0, "mcp_tool should be a valid hook type (Claude Code v2.1.118+)"); +} + #[test] fn test_cc_hk_016_unknown_type() { let content = r#"{ diff --git a/crates/agnix-rules/rules.json b/crates/agnix-rules/rules.json index aafc074fc..0262d5a18 100644 --- a/crates/agnix-rules/rules.json +++ b/crates/agnix-rules/rules.json @@ -1332,7 +1332,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-13", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1360,7 +1360,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-04", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1387,7 +1387,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-04", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1414,7 +1414,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-04", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1442,7 +1442,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-04", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1470,7 +1470,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-04", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1497,7 +1497,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-04", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1524,7 +1524,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-04", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1551,7 +1551,7 @@ "source_urls": [ "https://github.com/anthropics/claude-code/tree/main/.claude/commands" ], - "verified_on": "2026-02-09", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1578,7 +1578,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-04", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1606,7 +1606,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-04", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1634,7 +1634,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-04", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1661,7 +1661,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-07", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1689,7 +1689,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-07", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1717,7 +1717,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-07", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1745,7 +1745,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-07", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1773,7 +1773,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-07", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1801,7 +1801,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-07", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1829,7 +1829,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-14", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code", "file_types": [ @@ -1861,7 +1861,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-03-28", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code", "file_types": [ @@ -1892,7 +1892,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-03-28", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code", "file_types": [ @@ -1923,7 +1923,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-03-28", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code", "file_types": [ @@ -1955,7 +1955,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-03-28", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code", "file_types": [ @@ -1987,7 +1987,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-03-28", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code", "file_types": [ @@ -2019,7 +2019,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-03-28", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code", "file_types": [ diff --git a/knowledge-base/rules.json b/knowledge-base/rules.json index aafc074fc..0262d5a18 100644 --- a/knowledge-base/rules.json +++ b/knowledge-base/rules.json @@ -1332,7 +1332,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-13", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1360,7 +1360,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-04", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1387,7 +1387,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-04", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1414,7 +1414,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-04", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1442,7 +1442,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-04", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1470,7 +1470,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-04", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1497,7 +1497,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-04", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1524,7 +1524,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-04", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1551,7 +1551,7 @@ "source_urls": [ "https://github.com/anthropics/claude-code/tree/main/.claude/commands" ], - "verified_on": "2026-02-09", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1578,7 +1578,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-04", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1606,7 +1606,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-04", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1634,7 +1634,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-04", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1661,7 +1661,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-07", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1689,7 +1689,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-07", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1717,7 +1717,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-07", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1745,7 +1745,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-07", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1773,7 +1773,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-07", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1801,7 +1801,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-07", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code" }, @@ -1829,7 +1829,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-02-14", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code", "file_types": [ @@ -1861,7 +1861,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-03-28", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code", "file_types": [ @@ -1892,7 +1892,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-03-28", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code", "file_types": [ @@ -1923,7 +1923,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-03-28", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code", "file_types": [ @@ -1955,7 +1955,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-03-28", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code", "file_types": [ @@ -1987,7 +1987,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-03-28", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code", "file_types": [ @@ -2019,7 +2019,7 @@ "source_urls": [ "https://code.claude.com/docs/en/hooks" ], - "verified_on": "2026-03-28", + "verified_on": "2026-04-23", "applies_to": { "tool": "claude-code", "file_types": [ diff --git a/website/docs/rules/generated/cc-hk-001.md b/website/docs/rules/generated/cc-hk-001.md index e432e81a6..64e97fb20 100644 --- a/website/docs/rules/generated/cc-hk-001.md +++ b/website/docs/rules/generated/cc-hk-001.md @@ -13,7 +13,7 @@ keywords: ["CC-HK-001", "invalid hook event", "claude hooks", "validation", "agn - **Category**: `Claude Hooks` - **Normative Level**: `MUST` - **Auto-Fix**: `Yes (safe/unsafe)` -- **Verified On**: `2026-02-13` +- **Verified On**: `2026-04-23` ## Applicability diff --git a/website/docs/rules/generated/cc-hk-002.md b/website/docs/rules/generated/cc-hk-002.md index 1bea06e23..257019984 100644 --- a/website/docs/rules/generated/cc-hk-002.md +++ b/website/docs/rules/generated/cc-hk-002.md @@ -13,7 +13,7 @@ keywords: ["CC-HK-002", "prompt hook on wrong event", "claude hooks", "validatio - **Category**: `Claude Hooks` - **Normative Level**: `MUST` - **Auto-Fix**: `No` -- **Verified On**: `2026-02-04` +- **Verified On**: `2026-04-23` ## Applicability diff --git a/website/docs/rules/generated/cc-hk-003.md b/website/docs/rules/generated/cc-hk-003.md index aa4817527..cbcfba7e5 100644 --- a/website/docs/rules/generated/cc-hk-003.md +++ b/website/docs/rules/generated/cc-hk-003.md @@ -13,7 +13,7 @@ keywords: ["CC-HK-003", "matcher hint for tool events", "claude hooks", "validat - **Category**: `Claude Hooks` - **Normative Level**: `BEST_PRACTICE` - **Auto-Fix**: `No` -- **Verified On**: `2026-02-04` +- **Verified On**: `2026-04-23` ## Applicability diff --git a/website/docs/rules/generated/cc-hk-004.md b/website/docs/rules/generated/cc-hk-004.md index 9f9aab0c7..28eabed27 100644 --- a/website/docs/rules/generated/cc-hk-004.md +++ b/website/docs/rules/generated/cc-hk-004.md @@ -13,7 +13,7 @@ keywords: ["CC-HK-004", "matcher on non-tool event", "claude hooks", "validation - **Category**: `Claude Hooks` - **Normative Level**: `MUST` - **Auto-Fix**: `Yes (safe)` -- **Verified On**: `2026-02-04` +- **Verified On**: `2026-04-23` ## Applicability diff --git a/website/docs/rules/generated/cc-hk-005.md b/website/docs/rules/generated/cc-hk-005.md index f4e77c1dd..406f1a3d4 100644 --- a/website/docs/rules/generated/cc-hk-005.md +++ b/website/docs/rules/generated/cc-hk-005.md @@ -13,7 +13,7 @@ keywords: ["CC-HK-005", "missing type field", "claude hooks", "validation", "agn - **Category**: `Claude Hooks` - **Normative Level**: `MUST` - **Auto-Fix**: `Yes (safe)` -- **Verified On**: `2026-02-04` +- **Verified On**: `2026-04-23` ## Applicability diff --git a/website/docs/rules/generated/cc-hk-006.md b/website/docs/rules/generated/cc-hk-006.md index dd83a6b38..1362bf5b6 100644 --- a/website/docs/rules/generated/cc-hk-006.md +++ b/website/docs/rules/generated/cc-hk-006.md @@ -13,7 +13,7 @@ keywords: ["CC-HK-006", "missing command field", "claude hooks", "validation", " - **Category**: `Claude Hooks` - **Normative Level**: `MUST` - **Auto-Fix**: `No` -- **Verified On**: `2026-02-04` +- **Verified On**: `2026-04-23` ## Applicability diff --git a/website/docs/rules/generated/cc-hk-007.md b/website/docs/rules/generated/cc-hk-007.md index 2b67765d1..88655f0d5 100644 --- a/website/docs/rules/generated/cc-hk-007.md +++ b/website/docs/rules/generated/cc-hk-007.md @@ -13,7 +13,7 @@ keywords: ["CC-HK-007", "missing prompt field", "claude hooks", "validation", "a - **Category**: `Claude Hooks` - **Normative Level**: `MUST` - **Auto-Fix**: `No` -- **Verified On**: `2026-02-04` +- **Verified On**: `2026-04-23` ## Applicability diff --git a/website/docs/rules/generated/cc-hk-008.md b/website/docs/rules/generated/cc-hk-008.md index 409bd96a5..9a4a4b829 100644 --- a/website/docs/rules/generated/cc-hk-008.md +++ b/website/docs/rules/generated/cc-hk-008.md @@ -13,7 +13,7 @@ keywords: ["CC-HK-008", "script file not found", "claude hooks", "validation", " - **Category**: `Claude Hooks` - **Normative Level**: `MUST` - **Auto-Fix**: `No` -- **Verified On**: `2026-02-04` +- **Verified On**: `2026-04-23` ## Applicability diff --git a/website/docs/rules/generated/cc-hk-009.md b/website/docs/rules/generated/cc-hk-009.md index 070ecca4c..6e0b44225 100644 --- a/website/docs/rules/generated/cc-hk-009.md +++ b/website/docs/rules/generated/cc-hk-009.md @@ -13,7 +13,7 @@ keywords: ["CC-HK-009", "dangerous command pattern", "claude hooks", "validation - **Category**: `Claude Hooks` - **Normative Level**: `SHOULD` - **Auto-Fix**: `No` -- **Verified On**: `2026-02-09` +- **Verified On**: `2026-04-23` ## Applicability diff --git a/website/docs/rules/generated/cc-hk-010.md b/website/docs/rules/generated/cc-hk-010.md index 76e55d781..ef19c1dbf 100644 --- a/website/docs/rules/generated/cc-hk-010.md +++ b/website/docs/rules/generated/cc-hk-010.md @@ -13,7 +13,7 @@ keywords: ["CC-HK-010", "timeout policy", "claude hooks", "validation", "agnix", - **Category**: `Claude Hooks` - **Normative Level**: `SHOULD` - **Auto-Fix**: `Yes (safe)` -- **Verified On**: `2026-02-04` +- **Verified On**: `2026-04-23` ## Applicability diff --git a/website/docs/rules/generated/cc-hk-011.md b/website/docs/rules/generated/cc-hk-011.md index 950a92b29..8a4fa3106 100644 --- a/website/docs/rules/generated/cc-hk-011.md +++ b/website/docs/rules/generated/cc-hk-011.md @@ -13,7 +13,7 @@ keywords: ["CC-HK-011", "invalid timeout value", "claude hooks", "validation", " - **Category**: `Claude Hooks` - **Normative Level**: `MUST` - **Auto-Fix**: `Yes (unsafe)` -- **Verified On**: `2026-02-04` +- **Verified On**: `2026-04-23` ## Applicability diff --git a/website/docs/rules/generated/cc-hk-012.md b/website/docs/rules/generated/cc-hk-012.md index c3547742d..a83bea258 100644 --- a/website/docs/rules/generated/cc-hk-012.md +++ b/website/docs/rules/generated/cc-hk-012.md @@ -13,7 +13,7 @@ keywords: ["CC-HK-012", "hooks parse error", "claude hooks", "validation", "agni - **Category**: `Claude Hooks` - **Normative Level**: `MUST` - **Auto-Fix**: `No` -- **Verified On**: `2026-02-04` +- **Verified On**: `2026-04-23` ## Applicability diff --git a/website/docs/rules/generated/cc-hk-013.md b/website/docs/rules/generated/cc-hk-013.md index 300667676..2094eb092 100644 --- a/website/docs/rules/generated/cc-hk-013.md +++ b/website/docs/rules/generated/cc-hk-013.md @@ -13,7 +13,7 @@ keywords: ["CC-HK-013", "async on non-command hook", "claude hooks", "validation - **Category**: `Claude Hooks` - **Normative Level**: `MUST` - **Auto-Fix**: `Yes (safe)` -- **Verified On**: `2026-02-07` +- **Verified On**: `2026-04-23` ## Applicability diff --git a/website/docs/rules/generated/cc-hk-014.md b/website/docs/rules/generated/cc-hk-014.md index 8c0372943..817478417 100644 --- a/website/docs/rules/generated/cc-hk-014.md +++ b/website/docs/rules/generated/cc-hk-014.md @@ -13,7 +13,7 @@ keywords: ["CC-HK-014", "once outside skill/agent frontmatter", "claude hooks", - **Category**: `Claude Hooks` - **Normative Level**: `SHOULD` - **Auto-Fix**: `Yes (safe)` -- **Verified On**: `2026-02-07` +- **Verified On**: `2026-04-23` ## Applicability diff --git a/website/docs/rules/generated/cc-hk-015.md b/website/docs/rules/generated/cc-hk-015.md index ff35254b0..d4a147aac 100644 --- a/website/docs/rules/generated/cc-hk-015.md +++ b/website/docs/rules/generated/cc-hk-015.md @@ -13,7 +13,7 @@ keywords: ["CC-HK-015", "model on command hook", "claude hooks", "validation", " - **Category**: `Claude Hooks` - **Normative Level**: `MUST` - **Auto-Fix**: `Yes (safe)` -- **Verified On**: `2026-02-07` +- **Verified On**: `2026-04-23` ## Applicability diff --git a/website/docs/rules/generated/cc-hk-016.md b/website/docs/rules/generated/cc-hk-016.md index 25a636057..6cddc22ed 100644 --- a/website/docs/rules/generated/cc-hk-016.md +++ b/website/docs/rules/generated/cc-hk-016.md @@ -13,7 +13,7 @@ keywords: ["CC-HK-016", "validate hook type agent", "claude hooks", "validation" - **Category**: `Claude Hooks` - **Normative Level**: `MUST` - **Auto-Fix**: `Yes (unsafe)` -- **Verified On**: `2026-02-07` +- **Verified On**: `2026-04-23` ## Applicability diff --git a/website/docs/rules/generated/cc-hk-017.md b/website/docs/rules/generated/cc-hk-017.md index 01e07c94d..fd3313e46 100644 --- a/website/docs/rules/generated/cc-hk-017.md +++ b/website/docs/rules/generated/cc-hk-017.md @@ -13,7 +13,7 @@ keywords: ["CC-HK-017", "prompt/agent hook missing $arguments", "claude hooks", - **Category**: `Claude Hooks` - **Normative Level**: `SHOULD` - **Auto-Fix**: `Yes (safe)` -- **Verified On**: `2026-02-07` +- **Verified On**: `2026-04-23` ## Applicability diff --git a/website/docs/rules/generated/cc-hk-018.md b/website/docs/rules/generated/cc-hk-018.md index 3882bc481..d40c1b69c 100644 --- a/website/docs/rules/generated/cc-hk-018.md +++ b/website/docs/rules/generated/cc-hk-018.md @@ -13,7 +13,7 @@ keywords: ["CC-HK-018", "matcher on userpromptsubmit/stop", "claude hooks", "val - **Category**: `Claude Hooks` - **Normative Level**: `BEST_PRACTICE` - **Auto-Fix**: `Yes (safe)` -- **Verified On**: `2026-02-07` +- **Verified On**: `2026-04-23` ## Applicability diff --git a/website/docs/rules/generated/cc-hk-019.md b/website/docs/rules/generated/cc-hk-019.md index 9be3527d3..d28e624b1 100644 --- a/website/docs/rules/generated/cc-hk-019.md +++ b/website/docs/rules/generated/cc-hk-019.md @@ -13,7 +13,7 @@ keywords: ["CC-HK-019", "deprecated setup event", "claude hooks", "validation", - **Category**: `Claude Hooks` - **Normative Level**: `SHOULD` - **Auto-Fix**: `Yes (unsafe)` -- **Verified On**: `2026-02-14` +- **Verified On**: `2026-04-23` ## Applicability diff --git a/website/docs/rules/generated/cc-hk-020.md b/website/docs/rules/generated/cc-hk-020.md index b7d849399..7b8742c5f 100644 --- a/website/docs/rules/generated/cc-hk-020.md +++ b/website/docs/rules/generated/cc-hk-020.md @@ -13,7 +13,7 @@ keywords: ["CC-HK-020", "http hook missing url", "claude hooks", "validation", " - **Category**: `Claude Hooks` - **Normative Level**: `MUST` - **Auto-Fix**: `No` -- **Verified On**: `2026-03-28` +- **Verified On**: `2026-04-23` ## Applicability diff --git a/website/docs/rules/generated/cc-hk-021.md b/website/docs/rules/generated/cc-hk-021.md index 0ab4117cf..ee77cdb2f 100644 --- a/website/docs/rules/generated/cc-hk-021.md +++ b/website/docs/rules/generated/cc-hk-021.md @@ -13,7 +13,7 @@ keywords: ["CC-HK-021", "invalid if field", "claude hooks", "validation", "agnix - **Category**: `Claude Hooks` - **Normative Level**: `MUST` - **Auto-Fix**: `No` -- **Verified On**: `2026-03-28` +- **Verified On**: `2026-04-23` ## Applicability diff --git a/website/docs/rules/generated/cc-hk-022.md b/website/docs/rules/generated/cc-hk-022.md index cda2b4008..4246a64b4 100644 --- a/website/docs/rules/generated/cc-hk-022.md +++ b/website/docs/rules/generated/cc-hk-022.md @@ -13,7 +13,7 @@ keywords: ["CC-HK-022", "invalid shell value", "claude hooks", "validation", "ag - **Category**: `Claude Hooks` - **Normative Level**: `MUST` - **Auto-Fix**: `Yes (unsafe)` -- **Verified On**: `2026-03-28` +- **Verified On**: `2026-04-23` ## Applicability diff --git a/website/docs/rules/generated/cc-hk-023.md b/website/docs/rules/generated/cc-hk-023.md index ae0a6b2de..a75b4b7ee 100644 --- a/website/docs/rules/generated/cc-hk-023.md +++ b/website/docs/rules/generated/cc-hk-023.md @@ -13,7 +13,7 @@ keywords: ["CC-HK-023", "once field not boolean", "claude hooks", "validation", - **Category**: `Claude Hooks` - **Normative Level**: `MUST` - **Auto-Fix**: `Yes (unsafe)` -- **Verified On**: `2026-03-28` +- **Verified On**: `2026-04-23` ## Applicability diff --git a/website/docs/rules/generated/cc-hk-024.md b/website/docs/rules/generated/cc-hk-024.md index af147512c..7588b512e 100644 --- a/website/docs/rules/generated/cc-hk-024.md +++ b/website/docs/rules/generated/cc-hk-024.md @@ -13,7 +13,7 @@ keywords: ["CC-HK-024", "headers missing allowedenvvars", "claude hooks", "valid - **Category**: `Claude Hooks` - **Normative Level**: `SHOULD` - **Auto-Fix**: `Yes (safe)` -- **Verified On**: `2026-03-28` +- **Verified On**: `2026-04-23` ## Applicability diff --git a/website/docs/rules/generated/cc-hk-025.md b/website/docs/rules/generated/cc-hk-025.md index 4318b0eeb..d883c3a5d 100644 --- a/website/docs/rules/generated/cc-hk-025.md +++ b/website/docs/rules/generated/cc-hk-025.md @@ -13,7 +13,7 @@ keywords: ["CC-HK-025", "invalid matcher value", "claude hooks", "validation", " - **Category**: `Claude Hooks` - **Normative Level**: `SHOULD` - **Auto-Fix**: `Yes (unsafe)` -- **Verified On**: `2026-03-28` +- **Verified On**: `2026-04-23` ## Applicability From 1ccc6fc0c7dcdb253c5dc12234555189de8f03fb Mon Sep 17 00:00:00 2001 From: Avi Fenesh Date: Thu, 23 Apr 2026 12:58:18 +0300 Subject: [PATCH 2/2] fix: expand mcp_tool to typed Hook enum + strengthen test (round 1 review) Reviewer feedback (PR #767 round 1): - Copilot CRITICAL: typed Hook enum still rejected mcp_tool, so even with the raw-JSON allow-list fix, CC-HK-012 (schema mismatch) would fire on every mcp_tool hook. Added Hook::McpTool variant with tool, if, timeout fields (per release notes). Updated all match arms (command(), prompt(), type_name(), the per-variant typed-validation block in rules/hooks/mod.rs); added is_mcp_tool() for symmetry. - Copilot: test was vacuous (only checked CC-HK-016 absence; would have passed with CC-HK-012 still firing). Strengthened to assert diagnostics.is_empty() (zero diagnostics of any rule). Now genuinely fails without the typed-enum addition. - Copilot: comment said 'follows the docs' but docs page hasn't been updated yet; reworded to 'follows the release notes' with a caveat about the doc gap as of 2026-04-23. - Gemini MEDIUM: KNOWN_KEYS dedup -> won't fix (project-wide pattern, same answer as PR #757/#763). Plus fmt drift caught by CI (cargo fmt forgotten after round-1 edit; same lesson as PR #758/#762). --- crates/agnix-core/src/rules/hooks/mod.rs | 8 ++++++ crates/agnix-core/src/rules/hooks/tests.rs | 23 +++++++++++------ crates/agnix-core/src/schemas/hooks.rs | 29 ++++++++++++++++++++-- 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/crates/agnix-core/src/rules/hooks/mod.rs b/crates/agnix-core/src/rules/hooks/mod.rs index a0a416011..72e7752b2 100644 --- a/crates/agnix-core/src/rules/hooks/mod.rs +++ b/crates/agnix-core/src/rules/hooks/mod.rs @@ -684,6 +684,14 @@ impl Validator for HooksValidator { // CC-HK-020 url validation, CC-HK-024 headers env vars. // No additional typed validation needed here. } + Hook::McpTool { .. } => { + // MCP tool hooks (added Claude Code v2.1.118) are + // validated at the raw JSON level via CC-HK-016 + // (string allow-list). Schema details for required + // fields are not yet documented at + // code.claude.com/docs/en/hooks (as of 2026-04-23); + // re-tighten when upstream docs land. + } } } } diff --git a/crates/agnix-core/src/rules/hooks/tests.rs b/crates/agnix-core/src/rules/hooks/tests.rs index 45c52017e..c557f03c7 100644 --- a/crates/agnix-core/src/rules/hooks/tests.rs +++ b/crates/agnix-core/src/rules/hooks/tests.rs @@ -2954,12 +2954,16 @@ fn test_cc_hk_016_agent_type_valid() { fn test_cc_hk_016_mcp_tool_type_valid() { // Claude Code v2.1.118 added `type: "mcp_tool"` so hooks can invoke // MCP tools directly. Without this fix, agnix would false-positive - // CC-HK-016 (unknown hook type) on every v2.1.118+ user that follows - // the docs. + // CC-HK-016 (unknown hook type) AND CC-HK-012 (typed-schema mismatch) + // on every v2.1.118+ user that uses the new type per the release notes. + // (As of 2026-04-23 the docs page at code.claude.com/docs/en/hooks + // still lists only 4 types; the release notes are authoritative until + // the docs catch up.) let content = r#"{ "hooks": { "PostToolUse": [ { + "matcher": "Bash", "hooks": [ { "type": "mcp_tool", "tool": "mcp__server__do_thing" } ] @@ -2969,12 +2973,15 @@ fn test_cc_hk_016_mcp_tool_type_valid() { }"#; let diagnostics = validate(content); - let cc_hk_016: Vec<_> = diagnostics - .iter() - .filter(|d| d.rule == "CC-HK-016") - .collect(); - - assert_eq!(cc_hk_016.len(), 0, "mcp_tool should be a valid hook type (Claude Code v2.1.118+)"); + // Strict assertion: NO diagnostics should fire on a valid mcp_tool hook. + // This catches both CC-HK-016 (string allow-list) and CC-HK-012 + // (typed Hook enum schema match) -- without the typed-enum addition the + // raw JSON allow-list fix alone would not be enough. + assert!( + diagnostics.is_empty(), + "valid mcp_tool hook should produce zero diagnostics, got: {:?}", + diagnostics + ); } #[test] diff --git a/crates/agnix-core/src/schemas/hooks.rs b/crates/agnix-core/src/schemas/hooks.rs index d60dae612..23fdcb014 100644 --- a/crates/agnix-core/src/schemas/hooks.rs +++ b/crates/agnix-core/src/schemas/hooks.rs @@ -85,6 +85,23 @@ pub enum Hook { #[serde(skip_serializing_if = "Option::is_none")] timeout: Option, }, + /// MCP tool hook: invoke an MCP tool directly (Claude Code v2.1.118+). + /// Schema details for required/optional fields are not yet documented at + /// `code.claude.com/docs/en/hooks` (as of 2026-04-23); the typed schema + /// here uses `serde(other)` semantics so unknown future fields are + /// tolerated. Re-tighten when upstream docs land. + #[serde(rename = "mcp_tool")] + McpTool { + /// MCP tool reference, e.g. `mcp____` (per release notes) + #[serde(skip_serializing_if = "Option::is_none")] + tool: Option, + /// Optional `if` filter (same as other types) + #[serde(skip_serializing_if = "Option::is_none")] + r#if: Option, + /// Request timeout in seconds + #[serde(skip_serializing_if = "Option::is_none")] + timeout: Option, + }, } impl SettingsSchema { @@ -106,7 +123,9 @@ impl Hook { pub fn command(&self) -> Option<&str> { match self { Hook::Command { command, .. } => command.as_deref(), - Hook::Prompt { .. } | Hook::Agent { .. } | Hook::Http { .. } => None, + Hook::Prompt { .. } | Hook::Agent { .. } | Hook::Http { .. } | Hook::McpTool { .. } => { + None + } } } @@ -114,7 +133,7 @@ impl Hook { pub fn prompt(&self) -> Option<&str> { match self { Hook::Prompt { prompt, .. } | Hook::Agent { prompt, .. } => prompt.as_deref(), - Hook::Command { .. } | Hook::Http { .. } => None, + Hook::Command { .. } | Hook::Http { .. } | Hook::McpTool { .. } => None, } } @@ -145,8 +164,14 @@ impl Hook { Hook::Prompt { .. } => "prompt", Hook::Agent { .. } => "agent", Hook::Http { .. } => "http", + Hook::McpTool { .. } => "mcp_tool", } } + + #[allow(dead_code)] // schema-level API; validation uses Validator trait + pub fn is_mcp_tool(&self) -> bool { + matches!(self, Hook::McpTool { .. }) + } } impl HooksSchema {