feat: Support Extension Hooks with Security Warning#14460
Conversation
This change enables extensions to define hooks in 'hooks/hooks.json'. It includes:
- Loading and parsing of extension hooks in ExtensionManager.
- Variable substitution support (e.g., ${extensionPath}).
- Security warning in consent dialog when installing extensions with hooks.
- Comprehensive tests for hook loading and consent logic.
Summary of ChangesHello @abhipatel12, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a powerful new feature to Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces support for extension hooks, including loading hook configurations and adding a security consent flow. The changes are well-structured and the new tests provide good coverage. I have one main suggestion for the new loadExtensionHooks function in extension-manager.ts to improve its implementation by using asynchronous file I/O and strengthening input validation. This will enhance performance and robustness.
|
Size Change: +1.81 kB (+0.01%) Total Size: 21.5 MB
ℹ️ View Unchanged
|
PROBLEM
-------
Gemini CLI hooks were failing to find plugin root directory, causing hooks to
malfunction. Hook commands like `cat` were not being blocked.
ROOT CAUSE
----------
File: plugins/clautorun/hooks/hook_entry.py:100-122 (get_plugin_root())
Issue: Function fell back to os.getcwd() when env vars not set
Result: Returned project directory instead of plugin directory
WHY THIS FAILED
---------------
Gemini CLI doesn't set CLAUTORUN_PLUGIN_ROOT or CLAUDE_PLUGIN_ROOT environment
variables in hook commands. Documentation research showed:
- ${extensionPath} is substituted in command string before execution
- Environment variable assignment (VAR=value command) not supported
- Data should be passed via stdin JSON, not environment variables
Source: https://geminicli.com/docs/hooks/writing-hooks/
Source: google-gemini/gemini-cli#14460
WHAT CHANGED
------------
File: plugins/clautorun/hooks/hook_entry.py:117-125
- Added fallback: infer plugin_root from __file__ location
- This file is at <plugin_root>/hooks/hook_entry.py
- Calculate: plugin_root = dirname(dirname(__file__))
- Works for both Claude Code (env vars set) and Gemini CLI (no env vars)
File: ~/.gemini/extensions/cr/hooks/hooks.json (not committed)
- Removed CLAUTORUN_PLUGIN_ROOT=${extensionPath} prefix
- Commands now: "python3 ${extensionPath}/hooks/hook_entry.py"
- Gemini CLI substitutes ${extensionPath} before running command
VERIFICATION
------------
Debug test showed:
- Script path: /Users/athundt/.gemini/extensions/cr/hooks/hook_entry.py
- Script dir: /Users/athundt/.gemini/extensions/cr/hooks
- CLAUTORUN_PLUGIN_ROOT: NOT SET (expected for Gemini)
- CLAUDE_PLUGIN_ROOT: NOT SET
- New logic: plugin_root = dirname(script_dir) = /Users/athundt/.gemini/extensions/cr
Test file: /tmp/gemini-hook-debug-v2.log
COMPATIBILITY
-------------
Works for both CLIs:
- Claude Code: Uses CLAUDE_PLUGIN_ROOT env var (path 1)
- Gemini CLI: Uses __file__ inference (path 3)
- Both CLIs: CLAUTORUN_PLUGIN_ROOT if set (path 2)
TESTING
-------
Debug hooks created:
- /Users/athundt/.gemini/extensions/cr/hooks/debug_hook.py
- /Users/athundt/.gemini/extensions/cr/hooks/debug_hook_v2.py
- /tmp/clautorun-gemini-test/test_debug_hooks.sh
- /tmp/clautorun-gemini-test/test_hooks_final.sh
Log files:
- /tmp/gemini-hook-debug.log
- /tmp/gemini-hook-debug-v2.log
REFERENCES
----------
- Gemini CLI Hooks: https://geminicli.com/docs/hooks/reference/
- Writing Hooks: https://geminicli.com/docs/hooks/writing-hooks/
- Extension Hooks PR: google-gemini/gemini-cli#14460
- Tool Names: https://geminicli.com/docs/tools/
NEXT STEPS
----------
Still investigating why hooks don't block commands in Gemini CLI sessions.
Extensions load correctly, hooks.json is valid, but BeforeTool not firing.
Need to verify hook execution chain and tool matcher patterns.
COMPLETED TASKS
---------------
1. Fixed source hooks.json to Claude Code format (task #20)
2. Updated GEMINI.md with required settings (task #25)
3. Created TROUBLESHOOTING.md guide (task #23)
4. Added comprehensive hooks format tests (TDD)
FILES CHANGED
-------------
File: plugins/clautorun/hooks/hooks.json
- Restored Claude Code format with CLAUDE_PLUGIN_ROOT
- Uses Claude event names: PreToolUse, PostToolUse, UserPromptSubmit, etc.
- Uses Claude tool names: Write, Bash, Edit, TaskCreate, etc.
- Timeout in seconds (10s), not milliseconds
File: plugins/clautorun/GEMINI.md
- Added "Required Settings" section
- Documents enableHooks + enableMessageBusIntegration requirement
- Includes version requirement (v0.28.0+)
- Shows update commands using Bun or npm
File: plugins/clautorun/TROUBLESHOOTING.md (NEW - 500+ lines)
- Hooks not firing checklist
- Hook execution errors debugging
- Wrong hook version fix
- Command blocking troubleshooting
- CLI-specific issues (Gemini vs Claude)
- Debug logging guides
- Known issues from GitHub
File: plugins/clautorun/tests/test_hooks_format.py (NEW - 11 tests, all passing)
TDD approach:
- RED: Tests initially would fail with wrong hooks.json
- GREEN: Tests now pass after fixing hooks.json
- REFACTOR: Comprehensive coverage of both formats
Test coverage:
✅ Claude hooks use ${CLAUDE_PLUGIN_ROOT}
✅ Claude hooks use Claude event names
✅ Claude hooks use Claude tool names
✅ Gemini hooks use ${extensionPath}
✅ Gemini hooks use Gemini event names
✅ Gemini hooks use Gemini tool names
✅ Gemini hooks have 'type' field
✅ Gemini hooks have timeout (milliseconds)
✅ Claude hooks timeout in seconds
✅ No env var assignment in Gemini hooks
✅ Both files are valid JSON
File: notes/2026_02_10_1948_gemini_hooks_integration_complete_notes.md (NEW)
- Comprehensive 700+ line documentation
- Complete history of debugging process
- Web research sources (14+ links)
- All root causes and solutions
- What's working vs remaining work
- Commits made and file changes
TESTING
-------
```bash
$ uv run pytest plugins/clautorun/tests/test_hooks_format.py -v
============================== 11 passed in 0.07s ==============================
```
All tests passing. Verifies:
- Source hooks.json is correct Claude Code format
- gemini-hooks.json is correct Gemini CLI format
- Formats are mutually exclusive
- Required fields present in each
REFERENCES
----------
Web research confirming fixes:
- Gemini CLI hooks: https://geminicli.com/docs/hooks/reference/
- Writing hooks: https://geminicli.com/docs/hooks/writing-hooks/
- Settings requirements: google-gemini/gemini-cli#13155
- Extension hooks: google-gemini/gemini-cli#14460
- Tool names: https://geminicli.com/docs/tools/
STATUS
------
Tasks completed: 3/13 (20, 23, 25)
Tests created: 11 new tests, all passing
Documentation: 2 new files (TROUBLESHOOTING.md, integration notes)
Hooks verified: Both Claude and Gemini formats correct
Next: Continue with remaining tasks using TDD approach
…overage
Follows the Bug Workaround Policy in plugins/autorun/CLAUDE.md so the split
layout has the same traceable deletion path as existing bug workarounds
(#4669 exit-2, #18534 additionalContext).
config.py:
- AUTORUN_BUG_CLAUDE_CODE_MARKETPLACE_SOURCE_SCAN_BUG_24115_WORKAROUND_ENABLED
- AUTORUN_BUG_GEMINI_CLI_HOOKS_JSON_HARDCODED_BUG_14449_WORKAROUND_ENABLED
Both default True. Env var overrides CONFIG. Each entry has bug link,
evidence note, deletion trigger, and override syntax.
install.py:
- Wrap helpers in `# --- BUG #24115 & #14449 WORKAROUND START/END ---`
so the entire block can be deleted when both upstream bugs ship fixes.
- _bug_24115_workaround_enabled() and _bug_14449_workaround_enabled()
read env-var → CONFIG → default True, matching existing #18534 pattern.
- _migrate_legacy_layout gates on both flags: disables silently when the
workaround is off (assumes legacy single-root layout is correct).
CLAUDE.md:
- Added both bugs to the workaround table with issue links, platform,
config key, default, and effect column.
- New 'Bug #24115 & #14449 in depth' section: root cause of each bug
independently, why one workaround solves both, how Pathway 2/6 shim
works, configuration examples, deletion instructions.
tests:
- test_split_layout.py gets a `BUG #24115 & #14449 TESTS START/END` block
with: config-key-exists, env-var-overrides-config, helpers-are-bracketed.
Uses shared _BUG_24115_FLAG / _BUG_14449_FLAG constants per policy.
- test_dual_cli_pathways.py (new, 23 tests) split into four classes:
TestGeminiPathway — template self-contained, hook_entry byte-matches
canonical, ${extensionPath} usage, event validity, timeout >=5000ms,
manifest forward-compat, repo-root shim resolution, install.py
references template not plugin root.
TestClaudePathway — plugin.json no hooks field, bug #24115 event
validity pin, ${CLAUDE_PLUGIN_ROOT} usage, legacy files absent,
_self_heal_cache_hooks and _clean_cross_cli_hooks removed.
TestSharedContract — hook_entry.py single source of truth, CLI type
detection via GEMINI_SESSION_ID, shared commands/ dir, SessionStart
registered in both.
TestBugWorkaroundCleanup — policy compliance (CONFIG + helpers +
bracketed block), upstream issue URLs present, CLAUDE.md policy
section exists.
Verified: 159 passed, 4 skipped across all affected test suites.
Refs: anthropics/claude-code#24115, google-gemini/gemini-cli#14449,
google-gemini/gemini-cli#14460
…ate sync Stale reference audit (found via grep for claude-hooks.json / ar-gemini / gemini-hooks.json / _clean_cross_cli_hooks / _self_heal_cache_hooks): - aix.toml:110 — `[hooks.gemini_cli] path = "plugins/autorun/hooks/gemini-hooks.json"` pointed at a file that does not exist post-refactor. Fixed to `plugins/autorun/src/autorun/gemini_template/hooks/hooks.json` so any AIX-driven install actually finds the Gemini hooks. - plugins/autorun/TROUBLESHOOTING.md:237 — user-facing verify-hooks.json instructions referenced the pre-split path layout. Updated both paths and added a pointer to the bug #24115/#14449 workaround notes. - plugins/autorun/tests/test_hooks_format.py — 15 error-message refs to "claude-hooks.json" cleaned up to "hooks.json (Claude)"; spot-fix for the one line that used the literal filename as a path component (line 653 `latest / "hooks" / "claude-hooks.json"` stayed as-is — that IS the legacy filename being tested-against). - plugins/autorun/tests/test_split_layout.py:82 — stale reference to the rejected `plugins/ar-gemini/hooks/hooks.json` plan. Repointed to the template path actually used. Edge-case hardening in aix_manifest.generate_manifests(): - NEW 3b step: shutil.copy2 of the canonical hook_entry.py into `plugins/autorun/src/autorun/gemini_template/hooks/hook_entry.py` on every install. This keeps Pathway 2/6 (direct `gemini extensions install <github-url>` or `.`) working without requiring `autorun --install` because the template now ships with a fresh hook_entry.py at commit time. - test_dual_cli_pathways.test_template_hook_entry_matches_canonical pins byte-equality so drift fails loudly at pytest time. HOOK_ARCHITECTURE.md new ASCII chart (replaces pre-refactor diagram): - Repo Layout & Why It's Split: shows dual-root layout (plugins/autorun/ vs plugins/autorun/src/autorun/gemini_template/) with repo-root shim symlinks (./gemini-extension.json, ./hooks/) and how they resolve into the template for Pathway 2/6. - Install Flow: diagram of autorun --install pipeline — find_marketplace_root → plugin_root resolution → _update_package_metadata → aix_manifest regen → Claude path (copytree to cache + substitute) vs Gemini path (_migrate_legacy_layout → _gemini_source → gemini extensions install → _copy_hook_entry_to_gemini_ext → TOML command gen). - Runtime Flow: slash command → UserPromptSubmit/BeforeAgent hook → Unix socket → daemon → integrations.py → exit-2 workaround for Claude / JSON decision for Gemini. - Why Two Roots table: cross-refs bug #24115 and #14449 to the specific layout decision that addresses each one. - Deletion Runbook: 9-step list for when both upstream bugs ship fixes. Verification: 274 passed, 4 skipped across 7 affected test suites. Full aix_manifest run still produces a consistent plugin.json + template + synced hook_entry.py. Refs: anthropics/claude-code#24115, google-gemini/gemini-cli#14449, google-gemini/gemini-cli#14460
Summary
This PR introduces support for extension-provided hooks in
gemini-cli, allowing extensions to define hooks (e.g.,BeforeTool,BeforeModel) via ahooks/hooks.jsonfile. It also implements a critical security warning mechanism to ensure users explicitly consent to installing extensions that contain executable hooks.Details
ExtensionManagerto discover and parsehooks/hooks.jsonin extension directories.${extensionPath}and${workspacePath}in hook definitions usingrecursivelyHydrateStrings.ExtensionManagerto detect hooks in new and updated extensions.consent.tsto display a warning ("ConfigSource.Extensionspriority in theHookRegistry.Related Issues
issue.md(Extension Hooks Support).How to Validate
"enableHooks": trueand"enableMessageBusIntegration": trueare set insettings.json.manual-test-extensionwithgemini-extension.jsonandhooks/hooks.json.hooks/hooks.jsonshould define aBeforeToolhook that runs a script.npm start -- extension install ./manual-test-extension.Pre-Merge Checklist
extension.test.tsandconsent.test.ts)