feat: wire Gemini CLI post-file-edit hook#648
feat: wire Gemini CLI post-file-edit hook#648khaong wants to merge 4 commits intorefactoring-transcript-readsfrom
Conversation
Add AfterTool matchers for write_file, edit_file, save_file, and replace that fire `entire hooks gemini post-file-edit`. The hook extracts file_path from tool_input (falling back to path/filename fields) and emits a FileEdit lifecycle event for real-time file tracking. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: c0d6b1e17aba
Log whether files came from hook tracking or transcript fallback, with counts from each source. Helps verify post-file-edit hook wiring in E2E. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: 53bde199cff1
PR SummaryMedium Risk Overview Implements Updates hook registry classification and expands unit/integration coverage, including new end-to-end Gemini file-tracking tests and updated hook-count expectations (12 → 16). Written by Cursor Bugbot for commit e568ab8. Configure here. |
There was a problem hiding this comment.
Pull request overview
Wires Gemini CLI’s post-file-edit hook into Entire’s lifecycle framework so file modifications can be tracked in real time (reducing reliance on transcript parsing), and updates tests/observability accordingly.
Changes:
- Add Gemini CLI hook installation entries (AfterTool matchers) for file-modifying tools and parse AfterTool hook payloads into
FileEditlifecycle events. - Add debug logging in
resolveFilesTouchedto distinguish hook-tracked files vs transcript-extraction fallback. - Update unit/integration tests to reflect the new hooks and parsing behavior.
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| cmd/entire/cli/strategy/manual_commit_hooks.go | Adds debug logs to clarify whether files came from hook tracking vs transcript fallback. |
| cmd/entire/cli/strategy/common_test.go | Minor formatting alignment in tests. |
| cmd/entire/cli/integration_test/agent_test.go | Updates Gemini hook installation expected count to include new matchers. |
| cmd/entire/cli/hook_registry.go | Treats post-file-edit as a tool hook type for logging/spans. |
| cmd/entire/cli/agent/geminicli/types.go | Adds AfterTool hook payload type and defines file-modification tool list. |
| cmd/entire/cli/agent/geminicli/lifecycle.go | Parses post-file-edit hook payload into agent.FileEdit events. |
| cmd/entire/cli/agent/geminicli/lifecycle_test.go | Adds unit tests for Gemini post-file-edit parsing (including fallback path fields). |
| cmd/entire/cli/agent/geminicli/hooks.go | Installs new AfterTool matchers to route file-modifying tools to post-file-edit. |
| cmd/entire/cli/agent/geminicli/hooks_test.go | Updates hook installation expectations and verifies the new AfterTool matchers. |
Comments suppressed due to low confidence (1)
cmd/entire/cli/agent/geminicli/hooks_test.go:138
- The current idempotency test covers “second install returns 0”, but there isn’t a regression test for upgrading an existing settings.json from the previous Gemini hook set (without post-file-edit matchers). Adding a test that starts with a pre-existing AfterTool config from the old version and asserts InstallHooks(non-force) adds the new matchers would prevent upgrade regressions.
// Second install should add 0 hooks
count2, err := agent.InstallHooks(context.Background(), false, false)
if err != nil {
t.Fatalf("second InstallHooks() error = %v", err)
}
| // File-edit hooks (AfterTool with specific matchers for file-modifying tools) | ||
| postFileEditCmd := cmdPrefix + "post-file-edit" | ||
| for _, toolName := range FileModificationTools { | ||
| afterTool = addGeminiHook(afterTool, toolName, "entire-post-file-edit-"+toolName, postFileEditCmd) | ||
| } |
There was a problem hiding this comment.
InstallHooks() can return 0 (idempotent) based solely on the existing SessionStart hook command, which means repos that already had the older Gemini hook set installed won’t get these newly-added post-file-edit AfterTool matchers unless users pass --force. Consider updating the idempotency check to verify that all required AfterTool matchers (including the new file-edit tool matchers) are present before early-returning 0, so upgrades happen automatically.
There was a problem hiding this comment.
The idempotency check uses the SessionStart command string — if the binary path matches, it early-returns. But addGeminiHook appends new matchers to the existing array regardless, so existing installs do get the new matchers on re-enable.
That said, there's a valid broader concern: users who never re-run entire enable won't get new hooks. We've noted this as future work — need a hook auto-update mechanism (version check or count comparison during session-start). Tracking separately from the per-agent rollout.
| // - pre-compress (1) | ||
| // - notification (1) | ||
| count := 12 | ||
| count := 16 |
There was a problem hiding this comment.
InstallHooks() returns a hard-coded hook count (16). Since this now depends on FileModificationTools, it’s easy for the count/comment/tests to drift if that list changes. Consider computing the return count from a base constant plus len(FileModificationTools) (or counting the hooks you add) to keep it correct automatically.
There was a problem hiding this comment.
Acknowledged — all agents currently hardcode their count and this matches the existing pattern. The comment enumerates every hook. A dynamic count is a nice-to-have but low priority given the pattern is consistent across agents.
| var toolInput fileEditToolInput | ||
| if err := json.Unmarshal(raw.ToolInput, &toolInput); err != nil { | ||
| return nil, nil //nolint:nilnil,nilerr // tool_input doesn't match expected schema | ||
| } |
There was a problem hiding this comment.
parseFileEdit silently drops events when tool_input can’t be unmarshaled, which makes it hard to diagnose schema mismatches (e.g., tool_input missing/null or using an unexpected structure). Consider adding a Debug log (similar to ClaudeCode’s parseFileEdit) that includes session_id and tool_name when Unmarshal fails, while still fail-opening (returning nil event).
There was a problem hiding this comment.
The debug log was already there (lines 212-215 in the diff). Added tool_name to it in e1ae412 for better diagnostics.
- Add debug logging in clearFilesTracking when getStateStore fails - Add debug logging in Gemini parseFileEdit on unmarshal failure - Add filename fallback test for fileEditToolInput.filePath() - Add Gemini-specific integration test for post-file-edit flow - Update stale resolveFilesTouched doc comment (three sources) - Fix comments: FilePath field, hook_registry, integration test helpers Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: 1cd4a907d16c
|
bugbot run |
| return "subagent" | ||
| case geminicli.HookNameBeforeTool, geminicli.HookNameAfterTool, | ||
| claudecode.HookNamePostFileEdit: | ||
| claudecode.HookNamePostFileEdit: // all agents use "post-file-edit"; only one constant needed (Go disallows duplicate case values) |
There was a problem hiding this comment.
getHookType() classifies "post-file-edit" hooks by referencing claudecode.HookNamePostFileEdit, which couples generic hook behavior to the Claude Code agent package. Since Gemini now defines HookNamePostFileEdit too, consider using the string literal "post-file-edit" (or a shared constant in a common package) in the switch to avoid cross-agent dependency and keep the comment accurate.
| claudecode.HookNamePostFileEdit: // all agents use "post-file-edit"; only one constant needed (Go disallows duplicate case values) | |
| "post-file-edit": // all agents use the "post-file-edit" hook name; match by literal to avoid cross-agent dependency |
There was a problem hiding this comment.
This reference predates this PR — it was introduced in #637 (the upstack). All agents define the same "post-file-edit" string value so Go's switch doesn't allow duplicates. A shared constant in a common package would be the cleaner long-term fix; will address in the base PR.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: a1d57ae9e8d0
Summary
post-file-edithook for Gemini CLI agent's file-modifying tools (write_file, edit_file, save_file, replace)file_path→path→filename)resolveFilesTouchedto distinguish hook-tracked files from transcript fallbackDetails
Part of the post-file-edit hook rollout (one PR per agent). Framework + Claude Code wiring in #637.
Gemini CLI's AfterTool hooks use matcher-based config in
.gemini/settings.json. Each file-modifying tool gets its own matcher entry that routes toentire hooks gemini-cli post-file-edit. The hook parses the tool input JSON to extract the file path, handling different field names across tools.Test plan
parseFileEditwith valid input, fallback path fields, and missing pathTestSingleSessionManualCommit --agent gemini-cli— confirmed hook fires and file trackedmise run fmt && mise run lint && mise run test:ci🤖 Generated with Claude Code