Skip to content

fix(hooks): avoid shell globs in root fallback - #90

Merged
intel352 merged 1 commit into
mainfrom
fix/hook-fallback-zsh-globs
Jun 23, 2026
Merged

intel352 merged 1 commit into
mainfrom
fix/hook-fallback-zsh-globs

Conversation

@intel352

Copy link
Copy Markdown
Contributor

Summary

  • remove hardcoded Claude marketplace fallback path from hook manifest commands
  • prefer Codex root env before Claude root env when resolving plugin roots
  • replace wildcard cache expansion with zsh-safe find-based Codex cache lookup
  • extend hook manifest command contract to execute under both sh and zsh

Root Cause

Codex executes hook commands through the user shell. On zsh, unmatched fallback globs such as ~/.claude/plugins/marketplace/autodev/* fail during shell expansion with exit code 1 before the hook wrapper runs.

Verification

  • zsh reproduction for Stop hook now exits 0 and emits JSON
  • bash tests/hook-contracts.sh

Copilot AI review requested due to automatic review settings June 23, 2026 02:22
@intel352
intel352 merged commit 2ffda57 into main Jun 23, 2026
11 checks passed
@intel352
intel352 deleted the fix/hook-fallback-zsh-globs branch June 23, 2026 02:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the hook manifest command strings to avoid zsh-failing glob expansion and to resolve plugin roots in a more host-agnostic way, then tightens the contract tests to exercise hook manifest commands under both sh and zsh.

Changes:

  • Reworked hooks/hooks.json command hooks to avoid wildcard-based fallback paths and use a find-based Codex cache lookup.
  • Adjusted plugin-root env var precedence when resolving the Autodev root (favoring CODEX_PLUGIN_ROOT earlier).
  • Extended tests/hook-contracts.sh to execute each manifest command under both sh and zsh (when available).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
tests/hook-contracts.sh Runs each manifest command under sh/zsh with plugin-root env vars unset and validates JSON stdout.
hooks/hooks.json Removes glob-based fallback expansion and adds a zsh-safe Codex cache directory selection before invoking hooks/run-hook.cmd.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/hook-contracts.sh
test_hooks_manifest_commands_do_not_require_claude_plugin_root() {
local tmp payload cmd count stdout_file stderr_file stdout_text status
test_hooks_manifest_commands_do_not_require_root_env_vars() {
local tmp payload cmd count stdout_file stderr_file stdout_text status shell_name shell_bin
Comment thread tests/hook-contracts.sh
Comment on lines +55 to +58
for shell_name in sh zsh; do
if ! shell_bin="$(command -v "$shell_name" 2>/dev/null)"; then
continue
fi
Comment thread tests/hook-contracts.sh
return
fi
pass "hooks.json commands: run without CLAUDE_PLUGIN_ROOT and emit JSON"
pass "hooks.json commands: run without plugin-root env vars and emit JSON under sh/zsh"
Comment thread hooks/hooks.json
{
"type": "command",
"command": "AUTODEV_HOOK=session-start; AUTODEV_ROOT=\"${AUTODEV_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}}}\"; for AUTODEV_ROOT_CAND in \"$AUTODEV_ROOT\" . \"$HOME/.codex/plugins/cache/autodev-marketplace/autodev\"/* \"$HOME/.claude/plugins/marketplace/autodev\"/*; do [ -n \"$AUTODEV_ROOT_CAND\" ] || continue; if [ -x \"$AUTODEV_ROOT_CAND/hooks/run-hook.cmd\" ]; then exec \"$AUTODEV_ROOT_CAND/hooks/run-hook.cmd\" \"$AUTODEV_HOOK\"; fi; done; printf '{}\\n'",
"command": "AUTODEV_HOOK=session-start; AUTODEV_ROOT=\"${AUTODEV_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-}}}}\"; run_autodev_hook() { AUTODEV_ROOT_CAND=\"$1\"; [ -n \"$AUTODEV_ROOT_CAND\" ] || return 1; if [ -x \"$AUTODEV_ROOT_CAND/hooks/run-hook.cmd\" ]; then exec \"$AUTODEV_ROOT_CAND/hooks/run-hook.cmd\" \"$AUTODEV_HOOK\"; fi; return 1; }; run_autodev_hook \"$AUTODEV_ROOT\"; run_autodev_hook .; AUTODEV_BASE=\"$HOME/.codex/plugins/cache/autodev-marketplace/autodev\"; if [ -d \"$AUTODEV_BASE\" ]; then AUTODEV_ROOT_CAND=$(find \"$AUTODEV_BASE\" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sort | tail -1); run_autodev_hook \"$AUTODEV_ROOT_CAND\"; fi; printf '{}\\n'",
Comment thread hooks/hooks.json
{
"type": "command",
"command": "AUTODEV_HOOK=session-start; AUTODEV_ROOT=\"${AUTODEV_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-}}}}\"; for AUTODEV_ROOT_CAND in \"$AUTODEV_ROOT\" . \"$HOME/.codex/plugins/cache/autodev-marketplace/autodev\"/* \"$HOME/.claude/plugins/marketplace/autodev\"/*; do [ -n \"$AUTODEV_ROOT_CAND\" ] || continue; if [ -x \"$AUTODEV_ROOT_CAND/hooks/run-hook.cmd\" ]; then exec \"$AUTODEV_ROOT_CAND/hooks/run-hook.cmd\" \"$AUTODEV_HOOK\"; fi; done; printf '{}\\n'",
"command": "AUTODEV_HOOK=session-start; AUTODEV_ROOT=\"${AUTODEV_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-}}}}\"; run_autodev_hook() { AUTODEV_ROOT_CAND=\"$1\"; [ -n \"$AUTODEV_ROOT_CAND\" ] || return 1; if [ -x \"$AUTODEV_ROOT_CAND/hooks/run-hook.cmd\" ]; then exec \"$AUTODEV_ROOT_CAND/hooks/run-hook.cmd\" \"$AUTODEV_HOOK\"; fi; return 1; }; run_autodev_hook \"$AUTODEV_ROOT\"; run_autodev_hook .; AUTODEV_BASE=\"$HOME/.codex/plugins/cache/autodev-marketplace/autodev\"; if [ -d \"$AUTODEV_BASE\" ]; then AUTODEV_ROOT_CAND=$(find \"$AUTODEV_BASE\" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sort | tail -1); run_autodev_hook \"$AUTODEV_ROOT_CAND\"; fi; printf '{}\\n'",
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.

2 participants