From 3f9e822b321cbf9db3188af3607754eac77ef742 Mon Sep 17 00:00:00 2001 From: rolf Date: Wed, 25 Mar 2026 20:37:51 -0300 Subject: [PATCH] fix(init): detect and clean up orphaned .github/hooks/rtk-rewrite.json Users who ran an earlier rtk init --copilot (from commit #605) ended up with .github/hooks/rtk-rewrite.json containing "command": "rtk hook" (missing subcommand). This causes RTK to log a spurious parse_failure on every Bash tool call, inflating rtk gain --failures stats silently. - rtk init --show: warns when the broken file is detected, with actionable guidance pointing to rtk gain --failures and the fix - rtk init -g --uninstall: removes the broken file as part of standard cleanup (only when command is the broken "rtk hook", leaves a correctly configured "rtk hook copilot" file untouched) Fixes #836 --- src/hooks/init.rs | 66 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/hooks/init.rs b/src/hooks/init.rs index 64115a1d..36b0fac9 100644 --- a/src/hooks/init.rs +++ b/src/hooks/init.rs @@ -582,6 +582,36 @@ pub fn uninstall(global: bool, gemini: bool, codex: bool, cursor: bool, verbose: removed.push("Integrity hash: removed".to_string()); } + // 1c. Remove orphaned .github/hooks/rtk-rewrite.json (broken command from old rtk init --copilot) + let github_hook_path = PathBuf::from(".github/hooks/rtk-rewrite.json"); + if github_hook_path.exists() { + let content = fs::read_to_string(&github_hook_path).unwrap_or_default(); + let command = serde_json::from_str::(&content) + .ok() + .and_then(|j| { + j.get("hooks")? + .get("PreToolUse")? + .as_array()? + .first() + .cloned() + }) + .and_then(|e| e.get("command")?.as_str().map(String::from)) + .unwrap_or_default(); + + if command == "rtk hook" { + fs::remove_file(&github_hook_path).with_context(|| { + format!( + "Failed to remove orphaned hook: {}", + github_hook_path.display() + ) + })?; + removed.push(format!( + "Orphaned Copilot hook: {} (broken \"rtk hook\" command)", + github_hook_path.display() + )); + } + } + // 2. Remove RTK.md let rtk_md_path = claude_dir.join("RTK.md"); if rtk_md_path.exists() { @@ -2017,6 +2047,42 @@ fn show_claude_config() -> Result<()> { println!("[--] Cursor: home dir not found"); } + // Check for orphaned .github/hooks/rtk-rewrite.json (generated by older rtk init --copilot) + let github_hook_path = PathBuf::from(".github/hooks/rtk-rewrite.json"); + if github_hook_path.exists() { + let content = fs::read_to_string(&github_hook_path).unwrap_or_default(); + let command = serde_json::from_str::(&content) + .ok() + .and_then(|j| { + j.get("hooks")? + .get("PreToolUse")? + .as_array()? + .first() + .cloned() + }) + .and_then(|e| e.get("command")?.as_str().map(String::from)) + .unwrap_or_default(); + + if command == "rtk hook" { + println!( + "\n[warn] .github/hooks/rtk-rewrite.json: broken hook command (\"rtk hook\" missing subcommand)" + ); + println!(" → Generated by an older version of rtk init --copilot"); + println!(" → Every Bash tool call logs a spurious parse_failure in history.db"); + println!(" → Check impact: rtk gain --failures"); + println!(" → Fix: delete the file or run `rtk init --copilot` to regenerate it"); + } else if command == "rtk hook copilot" { + println!( + "\n[ok] .github/hooks/rtk-rewrite.json: Copilot hook configured" + ); + } else if !command.is_empty() { + println!( + "\n[warn] .github/hooks/rtk-rewrite.json: unexpected command: \"{}\"", + command + ); + } + } + println!("\nUsage:"); println!(" rtk init # Full injection into local CLAUDE.md"); println!(" rtk init -g # Hook + RTK.md + @RTK.md + settings.json (recommended)");