diff --git a/codex/plugins/ox/.codex-plugin/plugin.json b/codex/plugins/ox/.codex-plugin/plugin.json index a98126a..f4e51ca 100644 --- a/codex/plugins/ox/.codex-plugin/plugin.json +++ b/codex/plugins/ox/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "ox", - "version": "0.0.22", + "version": "0.0.23", "description": "Base plugin — commit command, code quality hooks, auto-format and check hooks for all projects", "author": { "name": "Oxidian" diff --git a/codex/plugins/ox/hooks.json b/codex/plugins/ox/hooks.json index b62db17..10f7686 100644 --- a/codex/plugins/ox/hooks.json +++ b/codex/plugins/ox/hooks.json @@ -6,7 +6,7 @@ "hooks": [ { "type": "command", - "command": "sh -c 'root=\"$(git rev-parse --show-toplevel 2>/dev/null || pwd)\"; bootstrap_dir=\"${CODEX_PLUGINS_BOOTSTRAP_DIR:-.codex/cc-plugins}\"; case \"$bootstrap_dir\" in /*) bootstrap_root=\"$bootstrap_dir\" ;; *) bootstrap_root=\"$root/$bootstrap_dir\" ;; esac; bootstrap_runner=\"$bootstrap_root/codex/plugins/ox/scripts/run_if_changed.py\"; repo_runner=\"$root/codex/plugins/ox/scripts/run_if_changed.py\"; cache_runner=\"$HOME/.codex/plugins/cache/oxidian/ox/0.0.22/scripts/run_if_changed.py\"; if [ -f \"$bootstrap_runner\" ]; then runner=\"$bootstrap_runner\"; elif [ -f \"$repo_runner\" ]; then runner=\"$repo_runner\"; elif [ -f \"$cache_runner\" ]; then runner=\"$cache_runner\"; else echo \"ox hook runner not found; checked $bootstrap_runner, $repo_runner, and $cache_runner\" >&2; exit 2; fi; exec python3 \"$runner\" --runtime codex --action fast'", + "command": "sh -c 'root=\"$(git rev-parse --show-toplevel 2>/dev/null || pwd)\"; bootstrap_dir=\"${CODEX_PLUGINS_BOOTSTRAP_DIR:-.codex/cc-plugins}\"; case \"$bootstrap_dir\" in /*) bootstrap_root=\"$bootstrap_dir\" ;; *) bootstrap_root=\"$root/$bootstrap_dir\" ;; esac; bootstrap_runner=\"$bootstrap_root/codex/plugins/ox/scripts/run_if_changed.py\"; repo_runner=\"$root/codex/plugins/ox/scripts/run_if_changed.py\"; cache_runner=\"$HOME/.codex/plugins/cache/oxidian/ox/0.0.23/scripts/run_if_changed.py\"; if [ -f \"$bootstrap_runner\" ]; then runner=\"$bootstrap_runner\"; elif [ -f \"$repo_runner\" ]; then runner=\"$repo_runner\"; elif [ -f \"$cache_runner\" ]; then runner=\"$cache_runner\"; else echo \"ox hook runner not found; checked $bootstrap_runner, $repo_runner, and $cache_runner\" >&2; exit 2; fi; exec python3 \"$runner\" --runtime codex --action fast'", "timeout": 30, "statusMessage": "Running fast checks" } @@ -18,7 +18,7 @@ "hooks": [ { "type": "command", - "command": "sh -c 'root=\"$(git rev-parse --show-toplevel 2>/dev/null || pwd)\"; bootstrap_dir=\"${CODEX_PLUGINS_BOOTSTRAP_DIR:-.codex/cc-plugins}\"; case \"$bootstrap_dir\" in /*) bootstrap_root=\"$bootstrap_dir\" ;; *) bootstrap_root=\"$root/$bootstrap_dir\" ;; esac; bootstrap_runner=\"$bootstrap_root/codex/plugins/ox/scripts/run_if_changed.py\"; repo_runner=\"$root/codex/plugins/ox/scripts/run_if_changed.py\"; cache_runner=\"$HOME/.codex/plugins/cache/oxidian/ox/0.0.22/scripts/run_if_changed.py\"; if [ -f \"$bootstrap_runner\" ]; then runner=\"$bootstrap_runner\"; elif [ -f \"$repo_runner\" ]; then runner=\"$repo_runner\"; elif [ -f \"$cache_runner\" ]; then runner=\"$cache_runner\"; else echo \"ox hook runner not found; checked $bootstrap_runner, $repo_runner, and $cache_runner\" >&2; exit 2; fi; exec python3 \"$runner\" --runtime codex --action slow'", + "command": "sh -c 'root=\"$(git rev-parse --show-toplevel 2>/dev/null || pwd)\"; bootstrap_dir=\"${CODEX_PLUGINS_BOOTSTRAP_DIR:-.codex/cc-plugins}\"; case \"$bootstrap_dir\" in /*) bootstrap_root=\"$bootstrap_dir\" ;; *) bootstrap_root=\"$root/$bootstrap_dir\" ;; esac; bootstrap_runner=\"$bootstrap_root/codex/plugins/ox/scripts/run_if_changed.py\"; repo_runner=\"$root/codex/plugins/ox/scripts/run_if_changed.py\"; cache_runner=\"$HOME/.codex/plugins/cache/oxidian/ox/0.0.23/scripts/run_if_changed.py\"; if [ -f \"$bootstrap_runner\" ]; then runner=\"$bootstrap_runner\"; elif [ -f \"$repo_runner\" ]; then runner=\"$repo_runner\"; elif [ -f \"$cache_runner\" ]; then runner=\"$cache_runner\"; else echo \"ox hook runner not found; checked $bootstrap_runner, $repo_runner, and $cache_runner\" >&2; exit 2; fi; exec python3 \"$runner\" --runtime codex --action slow'", "timeout": 120, "statusMessage": "Running final checks" } diff --git a/codex/plugins/ox/scripts/run_if_changed.py b/codex/plugins/ox/scripts/run_if_changed.py index 0203962..788c5ba 100644 --- a/codex/plugins/ox/scripts/run_if_changed.py +++ b/codex/plugins/ox/scripts/run_if_changed.py @@ -432,6 +432,14 @@ def main() -> None: _emit(args.runtime, "Plan mode active, skipping") sys.exit(SUCCESS_CODE) + if ( + args.runtime == RUNTIME_CODEX + and args.action == "slow" + and hook_input + and hook_input.get("stop_hook_active") is True + ): + sys.exit(SUCCESS_CODE) + # Read config config_file = os.path.join(project_dir, CONFIG_PATH) if not os.path.exists(config_file): diff --git a/plugins/ox/.claude-plugin/plugin.json b/plugins/ox/.claude-plugin/plugin.json index f7b6ccb..ed2b898 100644 --- a/plugins/ox/.claude-plugin/plugin.json +++ b/plugins/ox/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "ox", "description": "Base plugin — commit command, code quality hooks, auto-format and check hooks for all projects", - "version": "0.0.22", + "version": "0.0.23", "author": { "name": "Oxidian" } diff --git a/plugins/ox/scripts/run_if_changed.py b/plugins/ox/scripts/run_if_changed.py index 0203962..788c5ba 100644 --- a/plugins/ox/scripts/run_if_changed.py +++ b/plugins/ox/scripts/run_if_changed.py @@ -432,6 +432,14 @@ def main() -> None: _emit(args.runtime, "Plan mode active, skipping") sys.exit(SUCCESS_CODE) + if ( + args.runtime == RUNTIME_CODEX + and args.action == "slow" + and hook_input + and hook_input.get("stop_hook_active") is True + ): + sys.exit(SUCCESS_CODE) + # Read config config_file = os.path.join(project_dir, CONFIG_PATH) if not os.path.exists(config_file): diff --git a/tests/ox/test_run_if_changed.py b/tests/ox/test_run_if_changed.py index 95c0b1b..5dbe87c 100644 --- a/tests/ox/test_run_if_changed.py +++ b/tests/ox/test_run_if_changed.py @@ -285,6 +285,20 @@ def test_success_has_no_output_and_derives_project_dir_from_cwd(self, tmp_path: assert result.stdout == "" assert result.stderr == "" + def test_reentrant_stop_skips_before_config_parsing(self, tmp_path: Path) -> None: + subprocess.run(["git", "init"], cwd=tmp_path, check=True, capture_output=True, text=True) + (tmp_path / ".claude").mkdir() + (tmp_path / ".claude" / "ox-hooks.json").write_text("{not json\n") + (tmp_path / "changed.txt").write_text("changed\n") + subdir = tmp_path / "subdir" + subdir.mkdir() + + result = _run_codex_hook(subdir, "slow", extra_payload={"stop_hook_active": True}) + + assert result.returncode == 0 + assert result.stdout == "" + assert result.stderr == "" + def test_failure_exits_two_with_feedback_on_stderr(self, tmp_path: Path) -> None: check_script = tmp_path / "check.py" check_script.write_text("import sys\nprint('bad check output')\nsys.exit(1)\n")