Conversation
There was a problem hiding this comment.
Pull request overview
This PR improves robustness of agent hook integrations by making production-installed hooks gracefully no-op (or emit a one-time session-start warning) when the entire CLI binary is missing from PATH, and by making stored hook JSON configs more readable by disabling HTML escaping.
Changes:
- Added shared production hook-command wrappers to warn once on session start and silently exit for other hooks when
entireis missing. - Updated multiple agent hook installers (and tests) to use the new wrappers in production mode.
- Stopped HTML-escaping JSON when writing hook config files and updated OpenCode plugin generation to include the missing-
entirewarning.
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| cmd/entire/cli/jsonutil/json.go | Disable HTML escaping for indented JSON output and add a no-escape marshal helper for compact JSON. |
| cmd/entire/cli/agent/hook_command.go | Introduce shared wrapper helpers for production hook commands (warn-once session-start and silent no-op). |
| cmd/entire/cli/agent/hook_command_test.go | Add unit tests for new hook command wrappers and warning formatting. |
| cmd/entire/cli/agent/opencode/plugin.go | Add placeholder for embedding a missing-entire warning in the generated plugin. |
| cmd/entire/cli/agent/opencode/hooks.go | Populate the new warning placeholder during plugin generation. |
| cmd/entire/cli/agent/opencode/entire_plugin.ts | Implement session-start warning and silent no-op behavior when entire is missing. |
| cmd/entire/cli/agent/opencode/hooks_test.go | Update OpenCode plugin tests for the new spawn/wrapper behavior and warning constant. |
| cmd/entire/cli/agent/geminicli/hooks.go | Wrap production hook commands; switch settings JSON writes to no-HTML-escape marshaling; adjust hook detection. |
| cmd/entire/cli/agent/geminicli/hooks_test.go | Update expected commands/fixtures to match wrapped production hooks. |
| cmd/entire/cli/agent/factoryaidroid/hooks.go | Wrap production hook commands; switch JSON writes to no-HTML-escape marshaling; adjust hook detection. |
| cmd/entire/cli/agent/factoryaidroid/hooks_test.go | Update expected commands to match wrapped production hooks. |
| cmd/entire/cli/agent/cursor/hooks.go | Wrap production hook commands; switch JSON writes to no-HTML-escape marshaling; adjust hook detection. |
| cmd/entire/cli/agent/cursor/hooks_test.go | Update expected commands to match wrapped production hooks. |
| cmd/entire/cli/agent/copilotcli/hooks.go | Wrap production hook commands; switch JSON writes to no-HTML-escape marshaling; adjust hook detection. |
| cmd/entire/cli/agent/copilotcli/hooks_test.go | Update expected commands to match wrapped production hooks. |
| cmd/entire/cli/agent/codex/hooks.go | Wrap production hook commands; switch JSON writes to no-HTML-escape marshaling; adjust hook detection. |
| cmd/entire/cli/agent/codex/hooks_test.go | Update assertions to account for wrapped production hook commands/warnings. |
| cmd/entire/cli/agent/claudecode/hooks.go | Wrap production hook commands; switch JSON writes to no-HTML-escape marshaling; adjust hook detection. |
| cmd/entire/cli/agent/claudecode/hooks_test.go | Update expected commands to match wrapped production hooks. |
Comments suppressed due to low confidence (6)
cmd/entire/cli/agent/geminicli/hooks.go:480
isEntireHookwas changed to usestrings.Contains(command, prefix)with very broad prefixes like"entire ". This will incorrectly classify user hooks such asecho "entire is great"as an Entire hook and remove them during force install/uninstall. Consider matching more precisely (e.g.,HasPrefixon trimmed command for raw commands, plus a separate check for wrapped commands likestrings.Contains(command, " exec entire ")/" exec go run ", or a stricter pattern that targetsentire hooks <agent>specifically).
// isEntireHook checks if a command is an Entire hook
func isEntireHook(command string) bool {
for _, prefix := range entireHookPrefixes {
if strings.Contains(command, prefix) {
return true
}
}
return false
cmd/entire/cli/agent/factoryaidroid/hooks.go:466
isEntireHooknow usesstrings.Contains(command, "entire "), which can match unrelated user commands that merely mention the word "entire" (e.g.,echo "entire..."). That can cause user-defined hooks to be deleted whenforceis used or during uninstall. Prefer a stricter match that targets actual hook invocations (e.g.,HasPrefixfor rawentire .../go run ...plus a narrower check for wrapped commands containingexec entire ...).
// isEntireHook checks if a command is an Entire hook
func isEntireHook(command string) bool {
for _, prefix := range entireHookPrefixes {
if strings.Contains(command, prefix) {
return true
}
}
return false
cmd/entire/cli/agent/cursor/hooks.go:369
isEntireHookswitched fromHasPrefixtoContainsusing prefixes like"entire ". This can incorrectly treat user hooks that mention "entire" anywhere in the command as managed by Entire, and remove them on force/uninstall. Use a more specific predicate (e.g., matchentire hooks cursor/go run ... hooks cursorat the start, or detect wrapped commands by looking forexec entire hooks cursorrather than anyentiresubstring).
func isEntireHook(command string) bool {
for _, prefix := range entireHookPrefixes {
if strings.Contains(command, prefix) {
return true
}
}
return false
cmd/entire/cli/agent/copilotcli/hooks.go:325
isEntireHooknow usesstrings.Contains(bash, prefix)whereprefixincludes"entire ". This will match unrelated user hook commands that merely contain the word "entire" (e.g.,echo "entire") and may remove them on force/uninstall. Suggest narrowing the match to actual Entire hook invocations (e.g., check for(^|\s)exec\s+entire\s+hooks\s+copilot-cli\borHasPrefixfor raw commands, plus a targeted check for the wrappedexec ...form).
// isEntireHook checks if a hook entry's bash command belongs to Entire.
func isEntireHook(bash string) bool {
for _, prefix := range entireHookPrefixes {
if strings.Contains(bash, prefix) {
return true
}
}
return false
cmd/entire/cli/agent/codex/hooks.go:306
isEntireHookusesstrings.Contains(command, "entire "), which can misclassify user hooks that mention "entire" somewhere in the command as Entire-managed and remove them duringforce/uninstall. Consider tightening the check to match actual hook invocations (e.g., rawHasPrefix("entire hooks codex ")/ local-dev prefix, and for wrapped commands check for"exec entire hooks codex "specifically).
func isEntireHook(command string) bool {
for _, prefix := range entireHookPrefixes {
if strings.Contains(command, prefix) {
return true
}
}
return false
cmd/entire/cli/agent/claudecode/hooks.go:470
isEntireHooknow matches prefixes viastrings.Contains, including the very generic"entire ". This can cause user hooks that simply mention "entire" to be treated as Entire hooks and removed on force/uninstall. Recommend a narrower match that specifically targetsentire hooks claude-code .../go run ${CLAUDE_PROJECT_DIR}... hooks claude-code ..., and separately recognizes the wrappedexec entire ...form rather than any substring match.
// isEntireHook checks if a command is an Entire hook (old or new format)
func isEntireHook(command string) bool {
for _, prefix := range entireHookPrefixes {
if strings.Contains(command, prefix) {
return true
}
}
return false
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit d2d1b8f. Configure here.
Entire-Checkpoint: 3600e9354bd4
This function was defined but never called by any agent — only exercised by its own unit test. Removing dead code to keep the public API surface accurate. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
WrapProductionJSONSessionStartHookCommand → WrapProductionJSONWarningHookCommand WrapProductionPlainTextSessionStartHookCommand → WrapProductionPlainTextWarningHookCommand These functions describe an output format (JSON vs plain text warning on stdout), not a lifecycle event. The Droid agent already uses the plain text variant on the Stop hook, making the old name actively confusing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Parse the hooks.json as structured data and assert exact command strings using the wrapper helpers, consistent with how all other agents' tests verify installed hooks. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Code review follow-ups:
|

Summary
Fixes #880.
Handle missing
entiregracefully across built-in agent hooks, and tighten managed-hook detection so Entire only matches its own direct or wrapped hook commands.What Changed
entirehandlingentireentireas plain textentireis unavailableVerification
go test ./cmd/entire/cli/agent/...Agent Examples
Claude Code

Codex

Gemini

Droid (on stop hooks)
