Skip to content

feat: add Copilot and Codex hook parity - #1788

Merged
shunkakinoki merged 7 commits into
mainfrom
codex/copilot-hooks
May 15, 2026
Merged

feat: add Copilot and Codex hook parity#1788
shunkakinoki merged 7 commits into
mainfrom
codex/copilot-hooks

Conversation

@shunkakinoki

@shunkakinoki shunkakinoki commented May 15, 2026

Copy link
Copy Markdown
Owner

Changes

  • Added Copilot managed config and activation alongside Codex/Claude hook setup.
  • Synced Copilot pre-tool hooks for dcg, RTK rewrite, security, protected git push, and GitHub repo settings blockers.
  • Kept Claude/Codex RTK rewrite hooks in sync while adding Copilot input/output handling.
  • Added Codex remote-control fish wrapper and enabled missing Codex feature flags in default and template config.

Testing

  • make format
  • make shell-lint
  • make shell-test
  • focused ShellSpec coverage for Copilot/Codex hook input formats

Generated with Codex.


Summary by cubic

Adds Copilot pre‑tool hooks with parity to Codex/Claude for RTK rewrite, security checks, and GitHub push/settings blockers. Also enables new Codex feature flags, registers dcg in both chains, and adds a stable fish corc wrapper for remote‑control.

  • New Features

    • Managed Copilot config + activation via Home Manager; installs pre‑tool hooks: RTK rewrite, dcg, security, and GitHub blockers.
    • RTK rewrite and security hooks parse Claude/Codex/Copilot inputs (object or string toolArgs) and emit Copilot‑style modifiedArgs/permission decisions.
    • Shared git push and gh settings blockers now parse Copilot input format.
    • Fish function corc runs Codex remote‑control using the resolved binary; tests included.
  • Bug Fixes

    • Keeps Claude and Codex RTK rewrite hooks in sync; minor activation cleanup for Copilot.
    • Makes Claude auto‑switch account count check non‑fatal.

Written for commit 7e1604c. Summary will update on new commits.

shunkakinoki and others added 7 commits May 15, 2026 20:33
- enable new Codex feature flags in managed config
- register dcg in Codex and Copilot pre-tool hooks
- let shared GitHub blockers parse Copilot hook input

Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
@indent-zero

indent-zero Bot commented May 15, 2026

Copy link
Copy Markdown
Contributor
PR Summary

Adds GitHub Copilot CLI hook parity with the existing Claude/Codex setup so the same RTK-rewrite, security, dcg, git-push, and gh-settings guardrails apply across all three CLIs. Also lights up several new Codex feature flags, registers dcg in the PreToolUse chain, and ships a corc fish wrapper for codex remote-control modeled after clrc.

  • New config/copilot/{activate.sh,config.json,default.nix} deploying a managed Copilot config to ~/.copilot/config.json (chmod 600) and symlinking rtk-rewrite.sh/security.sh from config/codex/hooks/.
  • Teaches rtk-rewrite.sh and security.sh to parse Copilot's toolName/toolArgs shape (both object and JSON-string forms) and emit Copilot-shaped {permissionDecision, modifiedArgs} output while preserving Claude/Codex output.
  • Extends shared block-git-push.sh and block-gh-settings.sh to additionally read .tool.input.command, .toolArgs.command, and .toolInput.command.
  • Registers dcg as a new PreToolUse hook in both Codex hooks.json (Bash matcher) and Copilot config.json.
  • Enables 9 new Codex [features] flags in config.toml/config.tpl.toml (apps_mcp_path_override, auth_elicitation, browser_use_external, builtin_mcp, enable_mcp_apps, plugin_hooks, remote_compaction_v2, responses_websocket_response_processed, terminal_resize_reflow).
  • Adds _corc_function fish wrapper + alias corc that resolves realpath (which codex) for a stable inode then runs codex remote-control $argv.
  • Fixes claude/hooks/auto-switch.sh count: || echo 0|| true (the old form produced "0\n0" on no-match and broke the -lt 2 check).
  • Adds spec coverage for Copilot input formats (object + string toolArgs), activate.sh overwrite behavior, dcg registration, claude/codex rtk sync, and the new fish wrapper.

Issues

6 potential issues found:

  • In config/copilot/config.json the dcg, block-git-push.sh, and block-gh-settings.sh hooks have no matcher, so Copilot will run them for every tool call (file reads, MCP calls, etc.) — breaking parity with config/codex/hooks.json where the entire chain sits under "matcher": "Bash". dcg is the most concerning since it runs unconditionally regardless of whether the tool is shell, while the two block-* scripts at least bail early on non-git/non-gh commands. → Autofix
  • spec/fish/_corc_function_test.fish "skips Claude-only worktree spawn flag" / "permission mode flag" assertions are tautologies — _corc_function never adds those flags and the tests don't pass them in either, so the assertions trivially pass without exercising any skip logic and would still pass even if --permission-mode auto were re-introduced into the function (because the second test invocation that does pass --enable is asserted against the first log file). → Autofix
  • Copilot string-form toolArgs slips past block-git-push.sh and block-gh-settings.sh — only .toolArgs.command (object form) is read, so when Copilot serializes toolArgs as a JSON-encoded string (the form the new security.sh/rtk-rewrite.sh specs explicitly cover) jq errors and command is empty, allowing git push origin main and destructive gh repo edit through both shared blockers. → Autofix
  • terminal_resize_reflow = true is inserted out of order in both config/codex/config.toml and config/codex/config.tpl.toml — the rest of [features] is alphabetical, so it should sit before tool_call_mcp_elicitation, not between tool_suggest and unavailable_dummy_tools. → Autofix
  • config/copilot/activate.sh cp -fs ~/.copilot/config.json on every home-manager activation, wiping any state Copilot writes there (auth, banner, prefs); the spec "replaces existing config with the managed config" locks this behavior in even though the default.nix comment notes "Copilot CLI mutates config.json". → Autofix
  • _corc_function doesn't guard against codex being missing — realpath (which codex) invokes realpath with no operand, errors, leaves $codex_real empty, and then tries to execute remote-control as a standalone command. → Autofix

CI Checks

Waiting for CI checks...


⚡ Autofix All Issues

@coderabbitai

coderabbitai Bot commented May 15, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added GitHub Copilot integration support with pre-tool security hooks.
    • Introduced corc shell abbreviation for simplified Codex remote-control access.
  • Improvements

    • Enhanced command parsing to support multiple input formats across Claude, Codex, and Copilot.
    • Expanded feature flags enabling authentication elicitation, browser capabilities, MCP apps, and terminal enhancements.
    • Improved fault tolerance in account detection logic.
  • Tests

    • Added comprehensive test coverage for new Copilot functionality and multi-format input handling.

Walkthrough

This PR extends the dotfiles configuration system to support GitHub Copilot alongside existing Claude Code and Codex integration. It generalizes hook scripts to handle multiple JSON input formats from different tools, adds Copilot-specific activation and configuration, enables new Codex feature flags, and provides comprehensive test coverage for cross-tool compatibility.

Changes

Copilot Integration and Multi-Tool Hook Harmonization

Layer / File(s) Summary
Copilot configuration and activation
config/copilot/activate.sh, config/copilot/config.json, config/copilot/default.nix, config/default.nix
Copilot runtime setup: activation script creates ~/.copilot, copies managed config with mode 600, registers pre-tool hooks (rtk-rewrite, security, shared blockers, dcg). Home Manager wiring persists the config via home.activation.copilotConfig and forced hook file overwrites.
RTK rewrite hook multi-format and Copilot output
config/claude/hooks/rtk-rewrite.sh, config/codex/hooks/rtk-rewrite.sh, spec/sync_rtk_rewrite_spec.sh
Both Claude and Codex rtk-rewrite.sh (kept identical via spec assertion) now extract commands from multiple JSON paths (tool.input.command, tool_input.command, toolArgs variants), rebuild input by preserving original fields and replacing only .command, and emit either Copilot permissionDecision (ask/allow with modifiedArgs) or Claude/Codex hookSpecificOutput format based on input shape.
Security hook multi-tool support
config/codex/hooks/security.sh
Extracts tool name from multiple JSON fields (.tool.name, .tool_name, .toolName); allows non-Bash tools to pass through; command extraction checks multiple paths and derives from toolArgs (object.command or JSON string via fromjson), enabling Copilot payload evaluation.
Shared hook command extraction
config/shared/hooks/block-gh-settings.sh, config/shared/hooks/block-git-push.sh
Both hooks update command extraction to check multiple JSON field paths, supporting Copilot toolArgs format alongside Claude Code and Codex shapes for consistent dangerous-command blocking.
Codex feature flag expansion
config/codex/config.toml, config/codex/config.tpl.toml, config/codex/hooks.json
Enable MCP (apps_mcp_path_override, builtin_mcp, enable_mcp_apps), browser/plugin features (auth_elicitation, browser_use_external, plugin_hooks), remote features (remote_compaction_v2, responses_websocket_response_processed), and terminal (terminal_resize_reflow); add conditional dcg command to Bash PreToolUse hook with 5-second timeout.
Claude auto-switch fault tolerance
config/claude/hooks/auto-switch.sh
Account counting uses || true instead of || echo 0 for graceful pipeline failure handling.
Fish shell Codex remote-control integration
home-manager/programs/fish/functions/_corc_function.fish, home-manager/programs/fish/default.nix
New _corc_function resolves Codex binary real path via realpath for stable inode, then invokes remote-control; corc abbreviation maps to the function.
Comprehensive test coverage
spec/activate_config_spec.sh, spec/codex_rtk_rewrite_spec.sh, spec/rtk_rewrite_spec.sh, spec/codex_security_spec.sh, spec/block_gh_settings_spec.sh, spec/block_git_push_spec.sh, spec/fish/_corc_function_test.fish, spec/coverage_spec.sh
Tests verify Copilot directory/config/hook setup, RTK rewrite output format branching for Copilot toolArgs (object and JSON string), security hook multi-tool pass-through and blocking, shared hook blocking with Copilot input, Fish stable binary resolution, and sync equality of Claude/Codex rtk-rewrite.sh.

Possibly Related PRs

  • shunkakinoki/dotfiles#1358: Also modifies config/claude/hooks/auto-switch.sh account-switching behavior, with this PR adjusting fault tolerance and that PR adding rate-limit-driven switching.
  • shunkakinoki/dotfiles#708: Both PRs update Codex feature flags in config/codex/config.toml to enable capabilities.
  • shunkakinoki/dotfiles#1297: This PR's hook updates (rtk-rewrite, security) build on that PR's Codex hook system and experimental features.

Suggested Labels

enhancement

Poem

🐰 A Copilot joins the warren beside Claude and Codex,
Unified hooks now parse their many formats,
Fish shortcuts hop faster through stable binaries,
Multi-tool harmony blooms in the dotfiles garden! 🌱


🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately summarizes the main objective: adding Copilot support with parity to existing Codex/Claude hooks.
Description check ✅ Passed The PR description clearly relates to the changeset, detailing the addition of Copilot configuration, hook synchronization, feature flags, and test coverage.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/copilot-hooks

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request extends the hook system—including rtk-rewrite, security, and shared blockers—to support GitHub Copilot and Codex input formats, and introduces a new corc fish function for remote control. Feedback highlights the need to handle non-object inputs in the rtk-rewrite hook to prevent crashes, the importance of maintaining numeric fallbacks in the auto-switch script, and a recommendation to use command -s instead of which for more reliable command resolution in fish scripts.

Comment on lines +90 to +100
ORIGINAL_INPUT=$(echo "$INPUT" | jq -c '
(
.tool_input
// .tool.input
// .toolArgs
// .toolInput
// {}
) | if type == "string" then (fromjson? // {}) else . end
')
UPDATED_INPUT=$(echo "$ORIGINAL_INPUT" | jq --arg cmd "$REWRITTEN" '.command = $cmd')
IS_COPILOT_INPUT=$(echo "$INPUT" | jq -r 'has("toolName") and has("toolArgs")')

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.

high

There are two issues in this block:

  1. The ORIGINAL_INPUT extraction (lines 90-98) is missing .command as a fallback source. This causes other top-level fields to be lost when the input is a simple command object (common in Codex), contradicting the goal of preserving original fields.
  2. The IS_COPILOT_INPUT check (line 100) will cause the script to crash if the input is a JSON string (e.g., "ls"), as has() only works on objects/arrays. Since set -e is active (line 32), the script will terminate prematurely.

Adding a type check and including .command in the fallback chain resolves both issues.

Suggested change
ORIGINAL_INPUT=$(echo "$INPUT" | jq -c '
(
.tool_input
// .tool.input
// .toolArgs
// .toolInput
// {}
) | if type == "string" then (fromjson? // {}) else . end
')
UPDATED_INPUT=$(echo "$ORIGINAL_INPUT" | jq --arg cmd "$REWRITTEN" '.command = $cmd')
IS_COPILOT_INPUT=$(echo "$INPUT" | jq -r 'has("toolName") and has("toolArgs")')
ORIGINAL_INPUT=$(echo "$INPUT" | jq -c '
(
.tool_input
// .tool.input
// .toolArgs
// .toolInput
// (if type == "object" and has("command") then . else {} end)
) | if type == "string" then (fromjson? // {}) else . end
')
UPDATED_INPUT=$(echo "$ORIGINAL_INPUT" | jq --arg cmd "$REWRITTEN" '.command = $cmd')
IS_COPILOT_INPUT=$(echo "$INPUT" | jq -r 'type == "object" and has("toolName") and has("toolArgs")')
References
  1. Scripts should handle failures gracefully to avoid premature termination, especially when set -e is active or when used in critical execution paths.

Comment on lines +90 to +100
ORIGINAL_INPUT=$(echo "$INPUT" | jq -c '
(
.tool_input
// .tool.input
// .toolArgs
// .toolInput
// {}
) | if type == "string" then (fromjson? // {}) else . end
')
UPDATED_INPUT=$(echo "$ORIGINAL_INPUT" | jq --arg cmd "$REWRITTEN" '.command = $cmd')
IS_COPILOT_INPUT=$(echo "$INPUT" | jq -r 'has("toolName") and has("toolArgs")')

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.

high

There are two issues in this block:

  1. The ORIGINAL_INPUT extraction (lines 90-98) is missing .command as a fallback source. This causes other top-level fields to be lost when the input is a simple command object (common in Codex), contradicting the goal of preserving original fields.
  2. The IS_COPILOT_INPUT check (line 100) will cause the script to crash if the input is a JSON string (e.g., "ls"), as has() only works on objects/arrays. Since set -e is active (line 32), the script will terminate prematurely.

Adding a type check and including .command in the fallback chain resolves both issues.

Suggested change
ORIGINAL_INPUT=$(echo "$INPUT" | jq -c '
(
.tool_input
// .tool.input
// .toolArgs
// .toolInput
// {}
) | if type == "string" then (fromjson? // {}) else . end
')
UPDATED_INPUT=$(echo "$ORIGINAL_INPUT" | jq --arg cmd "$REWRITTEN" '.command = $cmd')
IS_COPILOT_INPUT=$(echo "$INPUT" | jq -r 'has("toolName") and has("toolArgs")')
ORIGINAL_INPUT=$(echo "$INPUT" | jq -c '
(
.tool_input
// .tool.input
// .toolArgs
// .toolInput
// (if type == "object" and has("command") then . else {} end)
) | if type == "string" then (fromjson? // {}) else . end
')
UPDATED_INPUT=$(echo "$ORIGINAL_INPUT" | jq --arg cmd "$REWRITTEN" '.command = $cmd')
IS_COPILOT_INPUT=$(echo "$INPUT" | jq -r 'type == "object" and has("toolName") and has("toolArgs")')
References
  1. Scripts should handle failures gracefully to avoid premature termination, especially when set -e is active or when used in critical execution paths.


# Require at least 2 managed accounts
ACCOUNT_COUNT=$(cswap --list 2>/dev/null | grep -c '^\s*[0-9]' || echo 0)
ACCOUNT_COUNT=$(cswap --list 2>/dev/null | grep -c '^\s*[0-9]' || true)

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.

medium

Changing || echo 0 to || true reduces the robustness of the script. While grep -c typically outputs 0 even when no matches are found (exiting with status 1), using || true means that if grep fails to produce any output (e.g., due to an internal error or being killed), ACCOUNT_COUNT will be assigned an empty string. This will cause a syntax error in the subsequent numeric comparison [ "$ACCOUNT_COUNT" -lt 2 ]. Reverting to || echo 0 ensures a valid numeric fallback.

Suggested change
ACCOUNT_COUNT=$(cswap --list 2>/dev/null | grep -c '^\s*[0-9]' || true)
ACCOUNT_COUNT=$(cswap --list 2>/dev/null | grep -c '^\s*[0-9]' || echo 0)

# keeps its reference to the old inode and is unaffected.
# Usage: corc [<codex remote-control args...>]

set -l codex_real (realpath (which codex))

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.

medium

Using which in scripts is generally discouraged as its behavior varies across systems; command -s in fish is more reliable. Additionally, if codex is not found in the PATH, command -s will return nothing, causing realpath to fail with an error. It's safer to verify the command exists before attempting to resolve its path.

  set -l codex_path (command -s codex)
  if test -z "$codex_path"
    echo "Error: codex command not found" >&2
    return 1
  end
  set -l codex_real (realpath "$codex_path")

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@config/copilot/config.json`:
- Around line 11-15: The pre-tool hook that runs "command -v dcg >/dev/null 2>&1
&& dcg" is missing a matcher and thus runs for every tool; restrict it to
shell-only tools by adding a matcher entry to that hook (e.g., add a "matcher"
key that allows only shell/bash tools) so the command-type hook only executes
for shell tool invocations and does not run for non-shell tools.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fd2e008d-9ee7-48aa-a651-22d68ea7df27

📥 Commits

Reviewing files that changed from the base of the PR and between 4e14ac8 and 7e1604c.

📒 Files selected for processing (24)
  • config/claude/hooks/auto-switch.sh
  • config/claude/hooks/rtk-rewrite.sh
  • config/codex/config.toml
  • config/codex/config.tpl.toml
  • config/codex/hooks.json
  • config/codex/hooks/rtk-rewrite.sh
  • config/codex/hooks/security.sh
  • config/copilot/activate.sh
  • config/copilot/config.json
  • config/copilot/default.nix
  • config/default.nix
  • config/shared/hooks/block-gh-settings.sh
  • config/shared/hooks/block-git-push.sh
  • home-manager/programs/fish/default.nix
  • home-manager/programs/fish/functions/_corc_function.fish
  • spec/activate_config_spec.sh
  • spec/block_gh_settings_spec.sh
  • spec/block_git_push_spec.sh
  • spec/codex_rtk_rewrite_spec.sh
  • spec/codex_security_spec.sh
  • spec/coverage_spec.sh
  • spec/fish/_corc_function_test.fish
  • spec/rtk_rewrite_spec.sh
  • spec/sync_rtk_rewrite_spec.sh

Comment on lines +11 to +15
{
"type": "command",
"command": "command -v dcg >/dev/null 2>&1 && dcg",
"timeout": 5
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Restrict dcg to shell tools here too.

This is the only pre-tool hook in this chain without a matcher, so Copilot will run it for every tool call. The stack contract for this PR keeps dcg in the Bash-only path on the Codex side, so this currently breaks the parity goal and can make non-shell tools pay the hook cost or get blocked unexpectedly.

💡 Proposed fix
       {
         "type": "command",
+        "matcher": "bash|shell|Bash",
         "command": "command -v dcg >/dev/null 2>&1 && dcg",
         "timeout": 5
       },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{
"type": "command",
"command": "command -v dcg >/dev/null 2>&1 && dcg",
"timeout": 5
},
{
"type": "command",
"matcher": "bash|shell|Bash",
"command": "command -v dcg >/dev/null 2>&1 && dcg",
"timeout": 5
},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@config/copilot/config.json` around lines 11 - 15, The pre-tool hook that runs
"command -v dcg >/dev/null 2>&1 && dcg" is missing a matcher and thus runs for
every tool; restrict it to shell-only tools by adding a matcher entry to that
hook (e.g., add a "matcher" key that allows only shell/bash tools) so the
command-type hook only executes for shell tool invocations and does not run for
non-shell tools.

@cubic-dev-ai cubic-dev-ai Bot 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.

3 issues found across 24 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="config/codex/hooks/security.sh">

<violation number="1" location="config/codex/hooks/security.sh:12">
P1: Unsafe jq field access can abort the security hook when `.tool` is not an object. Guard the `.tool.name` lookup so mixed input shapes don't crash the hook.</violation>

<violation number="2" location="config/codex/hooks/security.sh:19">
P1: The first command-path in the jq fallback chain is not type-safe; it can fail fast and skip all fallback parsing.</violation>
</file>

<file name="config/shared/hooks/block-git-push.sh">

<violation number="1" location="config/shared/hooks/block-git-push.sh:17">
P1: This jq path can throw on valid inputs (e.g. when `tool` is a string), causing the hook to exit early under `set -euo pipefail` instead of reading fallback command fields.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

esac

command=$(echo "$input" | jq -r '
.tool.input.command

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.

P1: The first command-path in the jq fallback chain is not type-safe; it can fail fast and skip all fallback parsing.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At config/codex/hooks/security.sh, line 19:

<comment>The first command-path in the jq fallback chain is not type-safe; it can fail fast and skip all fallback parsing.</comment>

<file context>
@@ -1,18 +1,29 @@
+esac
+
+command=$(echo "$input" | jq -r '
+  .tool.input.command
+  // .tool_input.command
+  // (.toolArgs | if type == "object" then .command else empty end)
</file context>


command=$(echo "$input" | jq -r '.tool_input.command // empty' 2>/dev/null)
# Only process shell commands when the hook input includes a tool name.
tool_name=$(echo "$input" | jq -r '.tool.name // .tool_name // .toolName // empty' 2>/dev/null)

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.

P1: Unsafe jq field access can abort the security hook when .tool is not an object. Guard the .tool.name lookup so mixed input shapes don't crash the hook.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At config/codex/hooks/security.sh, line 12:

<comment>Unsafe jq field access can abort the security hook when `.tool` is not an object. Guard the `.tool.name` lookup so mixed input shapes don't crash the hook.</comment>

<file context>
@@ -1,18 +1,29 @@
-
-command=$(echo "$input" | jq -r '.tool_input.command // empty' 2>/dev/null)
+# Only process shell commands when the hook input includes a tool name.
+tool_name=$(echo "$input" | jq -r '.tool.name // .tool_name // .toolName // empty' 2>/dev/null)
+case "$tool_name" in
+"" | Bash | bash | shell) ;;
</file context>
Suggested change
tool_name=$(echo "$input" | jq -r '.tool.name // .tool_name // .toolName // empty' 2>/dev/null)
tool_name=$(echo "$input" | jq -r '(.tool | if type == "object" then .name else empty end) // .tool_name // .toolName // empty' 2>/dev/null)

# Extract command (works for both Claude and Codex input formats)
command=$(echo "$input" | jq -r '.tool_input.command // .command // empty' 2>/dev/null)
# Extract command (works for Claude, Codex, and Copilot hook input formats)
command=$(echo "$input" | jq -r '.tool.input.command // .tool_input.command // .toolArgs.command // .toolInput.command // .command // empty' 2>/dev/null)

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.

P1: This jq path can throw on valid inputs (e.g. when tool is a string), causing the hook to exit early under set -euo pipefail instead of reading fallback command fields.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At config/shared/hooks/block-git-push.sh, line 17:

<comment>This jq path can throw on valid inputs (e.g. when `tool` is a string), causing the hook to exit early under `set -euo pipefail` instead of reading fallback command fields.</comment>

<file context>
@@ -13,8 +13,8 @@ ALLOWED_REPOS=(
-# Extract command (works for both Claude and Codex input formats)
-command=$(echo "$input" | jq -r '.tool_input.command // .command // empty' 2>/dev/null)
+# Extract command (works for Claude, Codex, and Copilot hook input formats)
+command=$(echo "$input" | jq -r '.tool.input.command // .tool_input.command // .toolArgs.command // .toolInput.command // .command // empty' 2>/dev/null)
 [[ -z $command ]] && exit 0
 
</file context>
Suggested change
command=$(echo "$input" | jq -r '.tool.input.command // .tool_input.command // .toolArgs.command // .toolInput.command // .command // empty' 2>/dev/null)
command=$(echo "$input" | jq -r '.tool.input.command? // .tool_input.command? // .toolArgs.command? // .toolInput.command? // .command? // empty' 2>/dev/null)

# Extract command (works for both Claude and Codex input formats)
command=$(echo "$input" | jq -r '.tool_input.command // .command // empty' 2>/dev/null)
# Extract command (works for Claude, Codex, and Copilot hook input formats)
command=$(echo "$input" | jq -r '.tool.input.command // .tool_input.command // .toolArgs.command // .toolInput.command // .command // empty' 2>/dev/null)

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.

Copilot string toolArgs not blocked: this only reads .toolArgs.command (object form). When Copilot sends toolArgs as a JSON-encoded string — the variant config/codex/hooks/security.sh and config/codex/hooks/rtk-rewrite.sh explicitly handle via fromjson — jq errors with Cannot index string with string "command", the error is swallowed by 2>/dev/null, command is empty, and [[ -z $command ]] && exit 0 lets the push through.

Reproduce:

echo '{"toolName":"shell","toolArgs":"{\"command\":\"git push origin main\"}"}' \
  | jq -r '.tool.input.command // .tool_input.command // .toolArgs.command // .toolInput.command // .command // empty' 2>/dev/null
# -> empty

Replace the extraction with the same union expression used in config/codex/hooks/rtk-rewrite.sh:34-43 (object + string branches). Same fix needed in config/shared/hooks/block-gh-settings.sh:10.

CONFIG_JSON="$1"

mkdir -p ~/.copilot
cp -f "$CONFIG_JSON" ~/.copilot/config.json

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.

Wipes Copilot-written state on every activation: config/copilot/default.nix:3 calls out that "Copilot CLI mutates config.json", which is why a Nix symlink isn't used — but cp -f then unconditionally overwrites the runtime file every home-manager switch, blowing away whatever Copilot wrote (auth tokens, banner, model prefs, etc.). The new spec/activate_config_spec.sh "replaces existing config with the managed config" case explicitly tests-and-locks this: a pre-existing "banner": "never" and an extra hook are gone after activation.

If preserving Copilot's mutations matters, merge instead of overwrite, e.g.:

jq -s '.[0] * .[1]' ~/.copilot/config.json "$CONFIG_JSON" > ~/.copilot/config.json.new \
  && mv -f ~/.copilot/config.json.new ~/.copilot/config.json

(or restrict the merge to just hooks and disableAllHooks). If wholesale overwrite is intended, the default.nix comment should reflect that.

Comment thread config/codex/config.toml
tool_search = true
tool_search_always_defer_mcp_tools = true
tool_suggest = true
terminal_resize_reflow = true

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.

Out of alphabetical order — the rest of [features] is sorted, so this should go above tool_call_mcp_elicitation. Same fix needed in config/codex/config.tpl.toml.

},
{
"type": "command",
"command": "command -v dcg >/dev/null 2>&1 && dcg",

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.

Missing matcher breaks Copilot/Codex parity: this dcg hook (and the block-git-push.sh / block-gh-settings.sh entries below) has no matcher field, so Copilot will run it for every tool call — file reads, MCP, etc. — not just shell. The Codex chain in config/codex/hooks.json:22-65 keeps the entire group under "matcher": "Bash", so on Codex dcg only fires for Bash. Add "matcher": "bash|shell|Bash" here (and to the two block-* entries) to match the sibling rtk/security entries and the Codex chain.

{
  "type": "command",
  "matcher": "bash|shell|Bash",
  "command": "command -v dcg >/dev/null 2>&1 && dcg",
  "timeout": 5
}

@shunkakinoki
shunkakinoki merged commit ddd206f into main May 15, 2026
35 checks passed
@shunkakinoki
shunkakinoki deleted the codex/copilot-hooks branch May 15, 2026 13:24
@indent-zero indent-zero Bot mentioned this pull request May 16, 2026
shunkakinoki added a commit that referenced this pull request May 17, 2026
…1795)

* fix: add Copilot toolArgs/modifiedArgs support to rtk-rewrite hook

Tests added in #1788 check that rtk-rewrite.sh reads Copilot-format input
(.toolArgs as object or stringified JSON) and emits .modifiedArgs output,
but the hook itself was never updated, so the Shell workflow has been
failing on every push to main. #1794 then added two new copilot hook
files without updating the coverage list, adding a 5th failure.

- Read CMD from .tool_input.command OR .toolArgs.command (object) OR
  .toolArgs | fromjson | .command (string).
- Emit modifiedArgs alongside hookSpecificOutput when input used toolArgs,
  preserving original fields (e.g. timeout).
- Mirror to claude/codex/copilot copies (sync_rtk_rewrite_spec.sh enforces
  byte equality between claude and codex).
- Add config/copilot/hooks/{rtk-rewrite,security}.sh to coverage_spec.sh.

Note: scripts/sync-rtk-rewrite.sh fetches from upstream and overwrites all
three local copies — next sync will wipe this fix. Documented inline.

* fix(hooks): tolerant fromjson, exclude copilot from shfmt, preserve sync

Entire-Checkpoint: 9d826b214b88

* refactor(sync): drop patch file, make sync drift-check-only

Entire-Checkpoint: 8c976510b69a
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.

1 participant