Skip to content

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

Merged
shunkakinoki merged 3 commits into
mainfrom
claude/cranky-moser-aa3c37
May 17, 2026
Merged

fix: add Copilot toolArgs/modifiedArgs support to rtk-rewrite hook#1795
shunkakinoki merged 3 commits into
mainfrom
claude/cranky-moser-aa3c37

Conversation

@shunkakinoki
Copy link
Copy Markdown
Owner

@shunkakinoki shunkakinoki commented May 17, 2026

Summary

The Shell workflow has been failing on every push to main since #1788 (4 failures) and #1794 added a 5th. Both are pre-existing parity gaps from those merges, not regressions in main code — but they block the green-PR signal for unrelated work.

What was broken

What this PR changes

  • Hooks now parse the command from .tool_input.command OR .toolArgs.command (object) OR .toolArgs | fromjson | .command (string), reusing the multi-format jq pattern already present in config/copilot/hooks/security.sh.
  • Hooks now emit a top-level modifiedArgs field alongside hookSpecificOutput when input used toolArgs, preserving original fields like timeout. Claude/Codex output unchanged.
  • Mirrored byte-identically across claude / codex / copilot copies (sync_rtk_rewrite_spec.sh enforces this between claude and codex).
  • Added config/copilot/hooks/{rtk-rewrite,security}.sh to the coverage list.

Caveat (out of scope)

scripts/sync-rtk-rewrite.sh fetches from rtk-ai/rtk@master/.claude/hooks/rtk-rewrite.sh and overwrites all three local copies — next manual sync will wipe this patch. Documented inline in the hook header. Follow-up options: upstream the patch, post-process the sync, or pin a fork.

Test plan

  • shellspec spec/rtk_rewrite_spec.sh spec/codex_rtk_rewrite_spec.sh spec/coverage_spec.sh spec/sync_rtk_rewrite_spec.sh → 102/102 pass locally (was 5 failures on main).
  • Full shellspec → 1496/1496 pass (no other suites regressed).
  • CI Shell workflow turns green on this PR.

🤖 Generated with Claude Code


Summary by cubic

Adds Copilot toolArgs/modifiedArgs support to rtk-rewrite, makes parsing tolerant, and updates sync/coverage so Shell workflow failures are resolved.

  • Bug Fixes

    • Parse command from .tool_input.command or .toolArgs.command (object or JSON string via tolerant fromjson?).
    • Emit top-level modifiedArgs when input used toolArgs, preserving fields like timeout.
    • Mirror changes across claude, codex, and copilot hook copies and add Copilot hooks to coverage.
  • Tooling

    • Make scripts/sync-rtk-rewrite.sh drift-check-only (no overwrites); drop the patch step and print diffs to manually port upstream changes, preserving local Copilot support.
    • Keep claude and copilot hook files in sync via spec/sync_rtk_rewrite_spec.sh.
    • Exclude config/copilot/hooks/rtk-rewrite.sh from shfmt in treefmt.toml.

Written for commit 5e79309. Summary will update on new commits. Review in cubic

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.
@indent
Copy link
Copy Markdown
Contributor

indent Bot commented May 17, 2026

PR Summary

Fixes the Shell CI failures introduced when #1788 added Copilot-format test cases without updating the actual hook, and when #1794 added two copilot hook files without registering them in coverage. The rtk-rewrite.sh hook now accepts both Claude (.tool_input.command) and Copilot (.toolArgs as object or stringified JSON) payloads and emits the corresponding modifiedArgs alongside hookSpecificOutput, restoring green CI on main.

  • Extend CMD extraction in config/{claude,codex,copilot}/hooks/rtk-rewrite.sh to fall back from .tool_input.command to .toolArgs.command (object) to (.toolArgs | fromjson? | .command) (string).
  • Default ORIGINAL_INPUT to {} so pure-Copilot payloads (no .tool_input) still produce a valid updatedInput.
  • Build a top-level modifiedArgs from .toolArgs (preserving extras like timeout) and unify the Ask/Allow code paths into a single jq invocation that conditionally adds permissionDecision/modifiedArgs.
  • Keep the three hook copies byte-identical and add an inline header banner warning that scripts/sync-rtk-rewrite.sh will overwrite these additions on the next upstream sync.
  • Add config/copilot/hooks/rtk-rewrite.sh and config/copilot/hooks/security.sh to spec/coverage_spec.sh so the "no shell scripts are missing from coverage list" test matches git ls-files '*.sh' again.

Issues

1 potential issue found:

  • Stale doc comment: each hook copy still warns that scripts/sync-rtk-rewrite.sh will OVERWRITE the local additions on next run, but after this commit sync no longer overwrites — it only fetches upstream and diffs. The warning should be updated to match the new drift-check behavior. → Autofix
2 issues already resolved
  • Latent crash: MODIFIED_ARGS string branch uses fromjson (no ?), so a non-JSON .toolArgs string combined with a populated .tool_input.command aborts the hook with jq exit 5 — asymmetric with the CMD parser which tolerates the same input via fromjson?. The CMD parser at lines 42–47 has a related (smaller) gap if a JSON string parses to a non-object. (fixed by commit 8bc29d1)
  • spec/sync_rtk_rewrite_spec.sh only enforces byte equality between the claude and codex copies — the new copilot copy can silently drift in the future, which is exactly the divergence class this PR is fixing. (fixed by commit 8bc29d1)

CI Checks

Waiting for CI checks...


⚡ Autofix All Issues

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 17, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

Three RTK rewrite hook scripts (Claude, Codex, Copilot) are updated to extract commands from Copilot .toolArgs in addition to Claude .tool_input, conditionally emit Copilot-style modifiedArgs in the response, and adjust permission decision logic. A patch file and sync mechanism preserve these changes during upstream synchronization, while tests validate hook parity.

Changes

RTK Rewrite Copilot Support

Layer / File(s) Summary
Hook command extraction and output generation
config/claude/hooks/rtk-rewrite.sh, config/codex/hooks/rtk-rewrite.sh, config/copilot/hooks/rtk-rewrite.sh
All three RTK rewrite hooks expand command extraction to read from both .tool_input.command (Claude) and .toolArgs (Copilot, supporting object or JSON-string formats). Output generation is refactored to emit modifiedArgs when .toolArgs is present, preserve tool_input shape by defaulting to {}, and conditionally include permissionDecision for allow vs. ask cases.
Patch definition and sync preservation
scripts/rtk-rewrite.copilot.patch, scripts/sync-rtk-rewrite.sh
A patch file captures the Copilot-specific hook changes (command extraction, input/output shapes, and modifiedArgs generation). The sync script applies this patch to the downloaded upstream rtk-rewrite.sh before installing it into hook directories, preserving local Copilot divergence across upstream syncs.
Test coverage and configuration updates
spec/coverage_spec.sh, spec/sync_rtk_rewrite_spec.sh, treefmt.toml
Spec files verify Claude/Copilot hook parity and update coverage expectations to include config/copilot/hooks/rtk-rewrite.sh and config/copilot/hooks/security.sh. The formatter config excludes the Copilot hook from shfmt processing.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • shunkakinoki/dotfiles#1788: The main PR's rtk-rewrite hook changes (adding Copilot .toolArgs parsing and emitting Copilot-style modifiedArgs/updated hookSpecificOutput and permission decision) are directly aligned with PR #1788's "Copilot and Codex hook parity" updates to the same RTK rewrite hooks and output shapes.

  • shunkakinoki/dotfiles#1789: Both PRs modify the rtk-rewrite.sh hook logic around command extraction/output construction—main PR adds Copilot .toolArgs/modifiedArgs support, while #1789 removes that multi-shape/Copilot branching and restricts to .tool_input.command only.

Poem

A rabbit hops through hooks so bright, 🐰
Supporting Copilot's .toolArgs might,
With patches sync'd through every phase,
The rewrite hooks now blaze new ways! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: adding Copilot toolArgs/modifiedArgs support to the rtk-rewrite hook, which directly addresses the broken Shell workflow tests.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.
Description check ✅ Passed The PR description clearly explains the broken tests (#1788, #1794), the specific changes made to fix them (Copilot toolArgs/modifiedArgs support, coverage registration), and provides test results showing the fixes work locally.

✏️ 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 claude/cranky-moser-aa3c37

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.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 4 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/claude/hooks/rtk-rewrite.sh">

<violation number="1" location="config/claude/hooks/rtk-rewrite.sh:103">
P2: Use tolerant JSON parsing for string `toolArgs` when building `modifiedArgs`; strict `fromjson` can abort the hook under `set -e` on malformed input.</violation>
</file>

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

Comment thread config/claude/hooks/rtk-rewrite.sh Outdated
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

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 updates the rtk-rewrite.sh hooks for Claude, Codex, and Copilot to support Copilot's .toolArgs format, enabling command extraction and modification from both object and stringified JSON inputs. Feedback focuses on improving the robustness of jq parsing by adding type checks after fromjson to prevent errors on non-object values and optimizing performance by consolidating multiple jq process forks into a single call.

Comment thread config/claude/hooks/rtk-rewrite.sh
Comment on lines 93 to +127
# Build the updated tool_input with all original fields preserved, only command changed.
ORIGINAL_INPUT=$(echo "$INPUT" | jq -c '.tool_input')
# `// {}` handles pure-Copilot payloads that have no `.tool_input` field.
ORIGINAL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // {}')
UPDATED_INPUT=$(echo "$ORIGINAL_INPUT" | jq --arg cmd "$REWRITTEN" '.command = $cmd')

# Build Copilot-format `modifiedArgs` when input used `.toolArgs`. Preserves the
# original toolArgs structure (e.g. `timeout`) with `command` replaced.
TOOL_ARGS_KIND=$(echo "$INPUT" | jq -r '.toolArgs | type')
case "$TOOL_ARGS_KIND" in
object) MODIFIED_ARGS=$(echo "$INPUT" | jq -c --arg cmd "$REWRITTEN" '.toolArgs | .command = $cmd') ;;
string) MODIFIED_ARGS=$(echo "$INPUT" | jq -c --arg cmd "$REWRITTEN" '.toolArgs | fromjson | .command = $cmd') ;;
*) MODIFIED_ARGS="null" ;;
esac

if [ "$EXIT_CODE" -eq 3 ]; then
# Ask: rewrite the command, omit permissionDecision so Claude Code prompts.
jq -n \
--argjson updated "$UPDATED_INPUT" \
'{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"updatedInput": $updated
}
}'
DECISION=""
else
# Allow: output the rewrite instruction in Claude Code hook format.
jq -n \
--argjson updated "$UPDATED_INPUT" \
'{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "RTK auto-rewrite",
"updatedInput": $updated
}
}'
DECISION="allow"
fi

jq -n \
--argjson updated "$UPDATED_INPUT" \
--argjson modified "$MODIFIED_ARGS" \
--arg decision "$DECISION" \
'
({
hookSpecificOutput: (
{hookEventName: "PreToolUse", updatedInput: $updated}
+ (if $decision != "" then
{permissionDecision: $decision, permissionDecisionReason: "RTK auto-rewrite"}
else {} end)
)
})
+ (if $modified != null then {modifiedArgs: $modified} else {} end)
'
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

The current implementation uses multiple jq calls to construct the output JSON, which is inefficient in a shell script due to process forking. Additionally, the fromjson call on line 103 is potentially unsafe if .toolArgs is a string but not valid JSON.

Consolidating these into a single jq call improves performance and simplifies the logic while adding robustness to the JSON parsing.

Suggested change
# Build the updated tool_input with all original fields preserved, only command changed.
ORIGINAL_INPUT=$(echo "$INPUT" | jq -c '.tool_input')
# `// {}` handles pure-Copilot payloads that have no `.tool_input` field.
ORIGINAL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // {}')
UPDATED_INPUT=$(echo "$ORIGINAL_INPUT" | jq --arg cmd "$REWRITTEN" '.command = $cmd')
# Build Copilot-format `modifiedArgs` when input used `.toolArgs`. Preserves the
# original toolArgs structure (e.g. `timeout`) with `command` replaced.
TOOL_ARGS_KIND=$(echo "$INPUT" | jq -r '.toolArgs | type')
case "$TOOL_ARGS_KIND" in
object) MODIFIED_ARGS=$(echo "$INPUT" | jq -c --arg cmd "$REWRITTEN" '.toolArgs | .command = $cmd') ;;
string) MODIFIED_ARGS=$(echo "$INPUT" | jq -c --arg cmd "$REWRITTEN" '.toolArgs | fromjson | .command = $cmd') ;;
*) MODIFIED_ARGS="null" ;;
esac
if [ "$EXIT_CODE" -eq 3 ]; then
# Ask: rewrite the command, omit permissionDecision so Claude Code prompts.
jq -n \
--argjson updated "$UPDATED_INPUT" \
'{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"updatedInput": $updated
}
}'
DECISION=""
else
# Allow: output the rewrite instruction in Claude Code hook format.
jq -n \
--argjson updated "$UPDATED_INPUT" \
'{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "RTK auto-rewrite",
"updatedInput": $updated
}
}'
DECISION="allow"
fi
jq -n \
--argjson updated "$UPDATED_INPUT" \
--argjson modified "$MODIFIED_ARGS" \
--arg decision "$DECISION" \
'
({
hookSpecificOutput: (
{hookEventName: "PreToolUse", updatedInput: $updated}
+ (if $decision != "" then
{permissionDecision: $decision, permissionDecisionReason: "RTK auto-rewrite"}
else {} end)
)
})
+ (if $modified != null then {modifiedArgs: $modified} else {} end)
'
# Build the final output JSON, consolidating Claude and Copilot formats.
jq -n \
--argjson input "$INPUT" \
--arg rewritten "$REWRITTEN" \
--argjson is_ask "$([ "$EXIT_CODE" -eq 3 ] && echo true || echo false)" \
'
($input.tool_input // {} | .command = $rewritten) as $updated |
($input.toolArgs | if type == "object" then .command = $rewritten elif type == "string" then (fromjson? | if type == "object" then .command = $rewritten else null end) else null end) as $modified |
{
hookSpecificOutput: (
{hookEventName: "PreToolUse", updatedInput: $updated}
+ (if $is_ask then {} else {permissionDecision: "allow", permissionDecisionReason: "RTK auto-rewrite"} end)
)
}
+ (if $modified != null then {modifiedArgs: $modified} else {} end)
'

Comment thread config/codex/hooks/rtk-rewrite.sh
Comment on lines 93 to +127
# Build the updated tool_input with all original fields preserved, only command changed.
ORIGINAL_INPUT=$(echo "$INPUT" | jq -c '.tool_input')
# `// {}` handles pure-Copilot payloads that have no `.tool_input` field.
ORIGINAL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // {}')
UPDATED_INPUT=$(echo "$ORIGINAL_INPUT" | jq --arg cmd "$REWRITTEN" '.command = $cmd')

# Build Copilot-format `modifiedArgs` when input used `.toolArgs`. Preserves the
# original toolArgs structure (e.g. `timeout`) with `command` replaced.
TOOL_ARGS_KIND=$(echo "$INPUT" | jq -r '.toolArgs | type')
case "$TOOL_ARGS_KIND" in
object) MODIFIED_ARGS=$(echo "$INPUT" | jq -c --arg cmd "$REWRITTEN" '.toolArgs | .command = $cmd') ;;
string) MODIFIED_ARGS=$(echo "$INPUT" | jq -c --arg cmd "$REWRITTEN" '.toolArgs | fromjson | .command = $cmd') ;;
*) MODIFIED_ARGS="null" ;;
esac

if [ "$EXIT_CODE" -eq 3 ]; then
# Ask: rewrite the command, omit permissionDecision so Claude Code prompts.
jq -n \
--argjson updated "$UPDATED_INPUT" \
'{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"updatedInput": $updated
}
}'
DECISION=""
else
# Allow: output the rewrite instruction in Claude Code hook format.
jq -n \
--argjson updated "$UPDATED_INPUT" \
'{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "RTK auto-rewrite",
"updatedInput": $updated
}
}'
DECISION="allow"
fi

jq -n \
--argjson updated "$UPDATED_INPUT" \
--argjson modified "$MODIFIED_ARGS" \
--arg decision "$DECISION" \
'
({
hookSpecificOutput: (
{hookEventName: "PreToolUse", updatedInput: $updated}
+ (if $decision != "" then
{permissionDecision: $decision, permissionDecisionReason: "RTK auto-rewrite"}
else {} end)
)
})
+ (if $modified != null then {modifiedArgs: $modified} else {} end)
'
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

The current implementation uses multiple jq calls to construct the output JSON, which is inefficient in a shell script due to process forking. Additionally, the fromjson call on line 103 is potentially unsafe if .toolArgs is a string but not valid JSON.

Consolidating these into a single jq call improves performance and simplifies the logic while adding robustness to the JSON parsing.

Suggested change
# Build the updated tool_input with all original fields preserved, only command changed.
ORIGINAL_INPUT=$(echo "$INPUT" | jq -c '.tool_input')
# `// {}` handles pure-Copilot payloads that have no `.tool_input` field.
ORIGINAL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // {}')
UPDATED_INPUT=$(echo "$ORIGINAL_INPUT" | jq --arg cmd "$REWRITTEN" '.command = $cmd')
# Build Copilot-format `modifiedArgs` when input used `.toolArgs`. Preserves the
# original toolArgs structure (e.g. `timeout`) with `command` replaced.
TOOL_ARGS_KIND=$(echo "$INPUT" | jq -r '.toolArgs | type')
case "$TOOL_ARGS_KIND" in
object) MODIFIED_ARGS=$(echo "$INPUT" | jq -c --arg cmd "$REWRITTEN" '.toolArgs | .command = $cmd') ;;
string) MODIFIED_ARGS=$(echo "$INPUT" | jq -c --arg cmd "$REWRITTEN" '.toolArgs | fromjson | .command = $cmd') ;;
*) MODIFIED_ARGS="null" ;;
esac
if [ "$EXIT_CODE" -eq 3 ]; then
# Ask: rewrite the command, omit permissionDecision so Claude Code prompts.
jq -n \
--argjson updated "$UPDATED_INPUT" \
'{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"updatedInput": $updated
}
}'
DECISION=""
else
# Allow: output the rewrite instruction in Claude Code hook format.
jq -n \
--argjson updated "$UPDATED_INPUT" \
'{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "RTK auto-rewrite",
"updatedInput": $updated
}
}'
DECISION="allow"
fi
jq -n \
--argjson updated "$UPDATED_INPUT" \
--argjson modified "$MODIFIED_ARGS" \
--arg decision "$DECISION" \
'
({
hookSpecificOutput: (
{hookEventName: "PreToolUse", updatedInput: $updated}
+ (if $decision != "" then
{permissionDecision: $decision, permissionDecisionReason: "RTK auto-rewrite"}
else {} end)
)
})
+ (if $modified != null then {modifiedArgs: $modified} else {} end)
'
# Build the final output JSON, consolidating Claude and Copilot formats.
jq -n \
--argjson input "$INPUT" \
--arg rewritten "$REWRITTEN" \
--argjson is_ask "$([ "$EXIT_CODE" -eq 3 ] && echo true || echo false)" \
'
($input.tool_input // {} | .command = $rewritten) as $updated |
($input.toolArgs | if type == "object" then .command = $rewritten elif type == "string" then (fromjson? | if type == "object" then .command = $rewritten else null end) else null end) as $modified |
{
hookSpecificOutput: (
{hookEventName: "PreToolUse", updatedInput: $updated}
+ (if $is_ask then {} else {permissionDecision: "allow", permissionDecisionReason: "RTK auto-rewrite"} end)
)
}
+ (if $modified != null then {modifiedArgs: $modified} else {} end)
'

Comment thread config/copilot/hooks/rtk-rewrite.sh
Comment on lines 93 to +127
# Build the updated tool_input with all original fields preserved, only command changed.
ORIGINAL_INPUT=$(echo "$INPUT" | jq -c '.tool_input')
# `// {}` handles pure-Copilot payloads that have no `.tool_input` field.
ORIGINAL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // {}')
UPDATED_INPUT=$(echo "$ORIGINAL_INPUT" | jq --arg cmd "$REWRITTEN" '.command = $cmd')

# Build Copilot-format `modifiedArgs` when input used `.toolArgs`. Preserves the
# original toolArgs structure (e.g. `timeout`) with `command` replaced.
TOOL_ARGS_KIND=$(echo "$INPUT" | jq -r '.toolArgs | type')
case "$TOOL_ARGS_KIND" in
object) MODIFIED_ARGS=$(echo "$INPUT" | jq -c --arg cmd "$REWRITTEN" '.toolArgs | .command = $cmd') ;;
string) MODIFIED_ARGS=$(echo "$INPUT" | jq -c --arg cmd "$REWRITTEN" '.toolArgs | fromjson | .command = $cmd') ;;
*) MODIFIED_ARGS="null" ;;
esac

if [ "$EXIT_CODE" -eq 3 ]; then
# Ask: rewrite the command, omit permissionDecision so Claude Code prompts.
jq -n \
--argjson updated "$UPDATED_INPUT" \
'{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"updatedInput": $updated
}
}'
DECISION=""
else
# Allow: output the rewrite instruction in Claude Code hook format.
jq -n \
--argjson updated "$UPDATED_INPUT" \
'{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "RTK auto-rewrite",
"updatedInput": $updated
}
}'
DECISION="allow"
fi

jq -n \
--argjson updated "$UPDATED_INPUT" \
--argjson modified "$MODIFIED_ARGS" \
--arg decision "$DECISION" \
'
({
hookSpecificOutput: (
{hookEventName: "PreToolUse", updatedInput: $updated}
+ (if $decision != "" then
{permissionDecision: $decision, permissionDecisionReason: "RTK auto-rewrite"}
else {} end)
)
})
+ (if $modified != null then {modifiedArgs: $modified} else {} end)
'
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

The current implementation uses multiple jq calls to construct the output JSON, which is inefficient in a shell script due to process forking. Additionally, the fromjson call on line 103 is potentially unsafe if .toolArgs is a string but not valid JSON.

Consolidating these into a single jq call improves performance and simplifies the logic while adding robustness to the JSON parsing.

Suggested change
# Build the updated tool_input with all original fields preserved, only command changed.
ORIGINAL_INPUT=$(echo "$INPUT" | jq -c '.tool_input')
# `// {}` handles pure-Copilot payloads that have no `.tool_input` field.
ORIGINAL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // {}')
UPDATED_INPUT=$(echo "$ORIGINAL_INPUT" | jq --arg cmd "$REWRITTEN" '.command = $cmd')
# Build Copilot-format `modifiedArgs` when input used `.toolArgs`. Preserves the
# original toolArgs structure (e.g. `timeout`) with `command` replaced.
TOOL_ARGS_KIND=$(echo "$INPUT" | jq -r '.toolArgs | type')
case "$TOOL_ARGS_KIND" in
object) MODIFIED_ARGS=$(echo "$INPUT" | jq -c --arg cmd "$REWRITTEN" '.toolArgs | .command = $cmd') ;;
string) MODIFIED_ARGS=$(echo "$INPUT" | jq -c --arg cmd "$REWRITTEN" '.toolArgs | fromjson | .command = $cmd') ;;
*) MODIFIED_ARGS="null" ;;
esac
if [ "$EXIT_CODE" -eq 3 ]; then
# Ask: rewrite the command, omit permissionDecision so Claude Code prompts.
jq -n \
--argjson updated "$UPDATED_INPUT" \
'{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"updatedInput": $updated
}
}'
DECISION=""
else
# Allow: output the rewrite instruction in Claude Code hook format.
jq -n \
--argjson updated "$UPDATED_INPUT" \
'{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "RTK auto-rewrite",
"updatedInput": $updated
}
}'
DECISION="allow"
fi
jq -n \
--argjson updated "$UPDATED_INPUT" \
--argjson modified "$MODIFIED_ARGS" \
--arg decision "$DECISION" \
'
({
hookSpecificOutput: (
{hookEventName: "PreToolUse", updatedInput: $updated}
+ (if $decision != "" then
{permissionDecision: $decision, permissionDecisionReason: "RTK auto-rewrite"}
else {} end)
)
})
+ (if $modified != null then {modifiedArgs: $modified} else {} end)
'
# Build the final output JSON, consolidating Claude and Copilot formats.
jq -n \
--argjson input "$INPUT" \
--arg rewritten "$REWRITTEN" \
--argjson is_ask "$([ "$EXIT_CODE" -eq 3 ] && echo true || echo false)" \
'
($input.tool_input // {} | .command = $rewritten) as $updated |
($input.toolArgs | if type == "object" then .command = $rewritten elif type == "string" then (fromjson? | if type == "object" then .command = $rewritten else null end) else null end) as $modified |
{
hookSpecificOutput: (
{hookEventName: "PreToolUse", updatedInput: $updated}
+ (if $is_ask then {} else {permissionDecision: "allow", permissionDecisionReason: "RTK auto-rewrite"} end)
)
}
+ (if $modified != null then {modifiedArgs: $modified} else {} end)
'

Comment thread config/claude/hooks/rtk-rewrite.sh Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

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 `@scripts/sync-rtk-rewrite.sh`:
- Around line 19-21: The patch invocation using the variables PATCH and tmpfile
can prompt and hang CI; update the patch command in the block that checks [ -f
"$PATCH" ] to run non-interactively by adding the --batch and --forward flags
(so already-applied or reversed hunks are skipped without prompting) and
preserve the use of "<\"$PATCH\"" input redirection; ensure the surrounding
logic still checks the exit status of patch and fails fast if needed (refer to
the PATCH variable and tmpfile usage and the existing if [ -f "$PATCH" ]
conditional).
🪄 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: c101a2f3-34e2-437c-96a2-79ecaf432043

📥 Commits

Reviewing files that changed from the base of the PR and between f540871 and 8bc29d1.

📒 Files selected for processing (8)
  • config/claude/hooks/rtk-rewrite.sh
  • config/codex/hooks/rtk-rewrite.sh
  • config/copilot/hooks/rtk-rewrite.sh
  • scripts/rtk-rewrite.copilot.patch
  • scripts/sync-rtk-rewrite.sh
  • spec/coverage_spec.sh
  • spec/sync_rtk_rewrite_spec.sh
  • treefmt.toml

Comment thread scripts/sync-rtk-rewrite.sh Outdated
# LOCAL DIVERGENCE FROM UPSTREAM (rtk-ai/rtk @ master): this copy also reads
# commands from Copilot's `.toolArgs` (object or string) and emits a top-level
# `modifiedArgs` alongside `hookSpecificOutput`. scripts/sync-rtk-rewrite.sh will
# OVERWRITE these additions on next run — re-apply or upstream after syncing.
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.

Stale warning after the sync refactor: this banner still says scripts/sync-rtk-rewrite.sh will OVERWRITE these additions on next run, but after 5e79309 the sync script no longer overwrites — it only curls upstream and prints a diff. Worth updating the wording (e.g. “Run scripts/sync-rtk-rewrite.sh to diff against upstream and port any non-Copilot changes in manually”) so future readers don't think their local edits are fragile. Remember to mirror the edit byte-for-byte into the codex and copilot copies so spec/sync_rtk_rewrite_spec.sh stays green.

@shunkakinoki shunkakinoki merged commit 3edfc9f into main May 17, 2026
30 of 31 checks passed
@shunkakinoki shunkakinoki deleted the claude/cranky-moser-aa3c37 branch May 17, 2026 14:16
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