Skip to content

feat: Support Extension Hooks with Security Warning#14460

Merged
abhipatel12 merged 2 commits intomainfrom
abhipatel12/hooks-in-extensions
Dec 3, 2025
Merged

feat: Support Extension Hooks with Security Warning#14460
abhipatel12 merged 2 commits intomainfrom
abhipatel12/hooks-in-extensions

Conversation

@abhipatel12
Copy link
Copy Markdown
Contributor

@abhipatel12 abhipatel12 commented Dec 3, 2025

Summary

This PR introduces support for extension-provided hooks in gemini-cli, allowing extensions to define hooks (e.g., BeforeTool, BeforeModel) via a hooks/hooks.json file. It also implements a critical security warning mechanism to ensure users explicitly consent to installing extensions that contain executable hooks.

Details

  • Extension Hooks Loading: Modified ExtensionManager to discover and parse hooks/hooks.json in extension directories.
  • Variable Substitution: Added support for ${extensionPath} and ${workspacePath} in hook definitions using recursivelyHydrateStrings.
  • Security & Consent:
    • Updated ExtensionManager to detect hooks in new and updated extensions.
    • Modified consent.ts to display a warning ("⚠️ This extension contains Hooks which can automatically execute commands.") when hooks are present.
    • Enforces re-consent if an extension update adds hooks.
  • Integration: Extension hooks are registered with ConfigSource.Extensions priority in the HookRegistry.

Related Issues

  • Implements the feature requested in issue.md (Extension Hooks Support).

How to Validate

  1. Enable Hooks: Ensure "enableHooks": true and "enableMessageBusIntegration": true are set in settings.json.
  2. Create Test Extension: Create a directory manual-test-extension with gemini-extension.json and hooks/hooks.json.
    • hooks/hooks.json should define a BeforeTool hook that runs a script.
  3. Install Extension: Run npm start -- extension install ./manual-test-extension.
    • Verify: The consent prompt MUST show the warning: "⚠️ This extension contains Hooks which can automatically execute commands."
  4. Trigger Hook: Start a session and ask Gemini to run a command that uses a tool (e.g., "Please read the README and tell me what it says").
    • Verify: The hook script executes (check logs or output).

Pre-Merge Checklist

  • Added/updated tests (Added tests for hook loading and consent warning in extension.test.ts and consent.test.ts)
  • Noted breaking changes (None, purely additive)
  • Validated on required platforms/methods:
    • MacOS
      • npm run

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.
@abhipatel12 abhipatel12 requested a review from a team as a code owner December 3, 2025 19:02
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @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 gemini-cli by enabling extensions to define and execute custom hooks. This significantly enhances the extensibility of the CLI, allowing for more dynamic and integrated extension behaviors. Crucially, the change includes a robust security mechanism that warns users and requires explicit consent when installing or updating extensions that contain these executable hooks, ensuring transparency and user control over potentially impactful operations.

Highlights

  • Extension Hooks Support: Extensions can now define various hooks (e.g., BeforeTool, BeforeModel) through a hooks/hooks.json file, significantly enhancing extensibility.
  • Security Warning and Consent: A critical security warning is displayed to users during extension installation or update if the extension contains executable hooks, requiring explicit consent before proceeding.
  • Variable Substitution in Hooks: Hook definitions now support variable substitution for ${extensionPath} and ${workspacePath}, allowing for dynamic path resolution within hook commands.
  • Re-consent on Hook Addition: The system enforces re-consent if an existing extension is updated to include new hooks, ensuring users are always aware of changes to executable capabilities.
  • Hook Registry Integration: Loaded extension hooks are registered with the HookRegistry at ConfigSource.Extensions priority, ensuring proper execution order and management.
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

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.

Comment thread packages/cli/src/config/extension-manager.ts Outdated
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Dec 3, 2025

Size Change: +1.81 kB (+0.01%)

Total Size: 21.5 MB

Filename Size Change
./bundle/gemini.js 21.5 MB +1.81 kB (+0.01%)
ℹ️ View Unchanged
Filename Size
./bundle/sandbox-macos-permissive-closed.sb 1.03 kB
./bundle/sandbox-macos-permissive-open.sb 890 B
./bundle/sandbox-macos-permissive-proxied.sb 1.31 kB
./bundle/sandbox-macos-restrictive-closed.sb 3.29 kB
./bundle/sandbox-macos-restrictive-open.sb 3.36 kB
./bundle/sandbox-macos-restrictive-proxied.sb 3.56 kB

compressed-size-action

@abhipatel12 abhipatel12 added this pull request to the merge queue Dec 3, 2025
Merged via the queue into main with commit eb3312e Dec 3, 2025
33 of 35 checks passed
@abhipatel12 abhipatel12 deleted the abhipatel12/hooks-in-extensions branch December 3, 2025 20:21
ahundt added a commit to ahundt/autorun that referenced this pull request Feb 21, 2026
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.
ahundt added a commit to ahundt/autorun that referenced this pull request Feb 21, 2026
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
ahundt added a commit to ahundt/autorun that referenced this pull request Apr 26, 2026
…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
ahundt added a commit to ahundt/autorun that referenced this pull request Apr 26, 2026
…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
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.

4 participants