From afae65cd9847be117c2c5ef3e57db61a40e47ea2 Mon Sep 17 00:00:00 2001 From: Daniel Tipping Date: Wed, 1 Jul 2026 11:59:36 +0100 Subject: [PATCH] Skip Codex checks in plan mode --- codex/plugins/ox/.codex-plugin/plugin.json | 2 +- codex/plugins/ox/hooks.json | 4 +-- codex/plugins/ox/scripts/run_if_changed.py | 7 +++--- plugins/ox/.claude-plugin/plugin.json | 2 +- plugins/ox/scripts/run_if_changed.py | 7 +++--- tests/ox/test_run_if_changed.py | 29 ++++++++++++++++++++-- 6 files changed, 39 insertions(+), 12 deletions(-) diff --git a/codex/plugins/ox/.codex-plugin/plugin.json b/codex/plugins/ox/.codex-plugin/plugin.json index 6593290..ce2b5aa 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.20", + "version": "0.0.21", "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 c420273..9ea323d 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.20/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.21/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.20/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.21/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 2e75a80..93920a1 100644 --- a/codex/plugins/ox/scripts/run_if_changed.py +++ b/codex/plugins/ox/scripts/run_if_changed.py @@ -376,6 +376,10 @@ def main() -> None: if not project_dir: parser.error("--project-dir is required unless --runtime codex can derive cwd") + if args.runtime == RUNTIME_CODEX and hook_input and hook_input.get("permission_mode") == "plan": + _emit(args.runtime, "Plan mode active, skipping") + sys.exit(SUCCESS_CODE) + # Read config config_file = os.path.join(project_dir, CONFIG_PATH) if not os.path.exists(config_file): @@ -390,9 +394,6 @@ def main() -> None: _emit(args.runtime, f"No checks configured in {CONFIG_PATH}, skipping") sys.exit(SUCCESS_CODE) - if hook_input and hook_input.get("permission_mode") == "plan": - _emit(args.runtime, "Plan mode active, skipping") - sys.exit(SUCCESS_CODE) if hook_input and args.action == "slow" and _is_team_lead_session(session_id): _emit(args.runtime, "Agent team lead session, skipping stop checks") sys.exit(SUCCESS_CODE) diff --git a/plugins/ox/.claude-plugin/plugin.json b/plugins/ox/.claude-plugin/plugin.json index 1e9cbbf..eaccc2e 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.20", + "version": "0.0.21", "author": { "name": "Oxidian" } diff --git a/plugins/ox/scripts/run_if_changed.py b/plugins/ox/scripts/run_if_changed.py index 2e75a80..93920a1 100644 --- a/plugins/ox/scripts/run_if_changed.py +++ b/plugins/ox/scripts/run_if_changed.py @@ -376,6 +376,10 @@ def main() -> None: if not project_dir: parser.error("--project-dir is required unless --runtime codex can derive cwd") + if args.runtime == RUNTIME_CODEX and hook_input and hook_input.get("permission_mode") == "plan": + _emit(args.runtime, "Plan mode active, skipping") + sys.exit(SUCCESS_CODE) + # Read config config_file = os.path.join(project_dir, CONFIG_PATH) if not os.path.exists(config_file): @@ -390,9 +394,6 @@ def main() -> None: _emit(args.runtime, f"No checks configured in {CONFIG_PATH}, skipping") sys.exit(SUCCESS_CODE) - if hook_input and hook_input.get("permission_mode") == "plan": - _emit(args.runtime, "Plan mode active, skipping") - sys.exit(SUCCESS_CODE) if hook_input and args.action == "slow" and _is_team_lead_session(session_id): _emit(args.runtime, "Agent team lead session, skipping stop checks") sys.exit(SUCCESS_CODE) diff --git a/tests/ox/test_run_if_changed.py b/tests/ox/test_run_if_changed.py index 125ed92..91f16a3 100644 --- a/tests/ox/test_run_if_changed.py +++ b/tests/ox/test_run_if_changed.py @@ -81,12 +81,12 @@ def _init_branch_changed_repo(tmp_path: Path, command: str, *, base_ref: str = " return subdir -def _run_codex_hook(cwd: Path, action: str) -> subprocess.CompletedProcess[str]: +def _run_codex_hook(cwd: Path, action: str, *, permission_mode: str = "default") -> subprocess.CompletedProcess[str]: payload = { "session_id": f"test-{action}", "cwd": str(cwd), "hook_event_name": "Stop" if action == "slow" else "PostToolUse", - "permission_mode": "default", + "permission_mode": permission_mode, } return subprocess.run( [sys.executable, str(_script_path), "--runtime", "codex", "--action", action], @@ -289,6 +289,31 @@ def test_failure_exits_two_with_feedback_on_stderr(self, tmp_path: Path) -> None assert "Final checks failed. Fix these issues before finishing." in result.stderr assert "bad check output" in result.stderr + def test_plan_mode_slow_skips_failing_checks(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") + subdir = _init_changed_repo(tmp_path, _command_for_script(check_script)) + + result = _run_codex_hook(subdir, "slow", permission_mode="plan") + + assert result.returncode == 0 + assert result.stdout == "" + assert result.stderr == "" + + def test_plan_mode_slow_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", permission_mode="plan") + + assert result.returncode == 0 + assert result.stdout == "" + assert result.stderr == "" + def test_slow_runs_for_committed_branch_changes(self, tmp_path: Path) -> None: marker = tmp_path / "marker.txt" check_script = tmp_path / "check.py"