Skip to content

feat: add CodeBuddy Code support - #1967

Open
studyzy wants to merge 3 commits into
rtk-ai:developfrom
studyzy:develop
Open

studyzy wants to merge 3 commits into
rtk-ai:developfrom
studyzy:develop

Conversation

@studyzy

@studyzy studyzy commented May 19, 2026

Copy link
Copy Markdown

Closes #1966

Summary

Adds first-class support for CodeBuddy Code, a Claude-powered AI coding assistant whose config lives in .codebuddy/.

CodeBuddy Code uses the same PreToolUse hook JSON protocol as Claude Code, so the hook implementation delegates directly to the existing process_claude_payload logic.

Changes

New commands

  • rtk hook codebuddy — PreToolUse hook processor (reads JSON from stdin, rewrites tool_input.command)
  • rtk init -g --agent codebuddy — installs hook into ~/.codebuddy/settings.json and writes ~/.codebuddy/CODEBUDDY.md
  • rtk init -g --agent codebuddy --uninstall — removes all RTK artifacts

Files changed

File Change
src/hooks/constants.rs Add CODEBUDDY_DIR (.codebuddy) and CODEBUDDY_HOOK_COMMAND constants
src/hooks/hook_cmd.rs Add run_codebuddy() — delegates to process_claude_payload
src/hooks/init.rs Add run_codebuddy_mode(), uninstall_codebuddy(), and patch_settings_json_at() helper
src/main.rs Add AgentTarget::Codebuddy, HookCommands::Codebuddy, dispatch logic
hooks/codebuddy/rtk-awareness.md RTK awareness instructions embedded into ~/.codebuddy/CODEBUDDY.md
hooks/codebuddy/README.md Integration documentation

Design notes

  • Zero new protocol code: CodeBuddy Code uses the identical PreToolUse + tool_input.command JSON format as Claude Code, so run_codebuddy() simply calls process_claude_payload().
  • patch_settings_json_at(): Extracted a new helper that patches settings.json in an arbitrary directory (instead of hardcoding the Claude config dir). This makes it easy to add future agents with similar config layouts.
  • Global-only: Like Gemini and Cursor, CodeBuddy hooks are installed globally (~/.codebuddy/).

Testing

# Hook rewrite works correctly
echo '{"tool_name":"Bash","tool_input":{"command":"git status"}}' | rtk hook codebuddy
# Output: {"hookSpecificOutput":{"hookEventName":"PreToolUse","updatedInput":{"command":"rtk git status"}}}

# Dry-run init
rtk init -g --agent codebuddy --dry-run -v

# All 1901 existing tests pass
cargo test

@CLAassistant

CLAassistant commented May 19, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@studyzy

studyzy commented Jun 3, 2026

Copy link
Copy Markdown
Author

Hi maintainers 👋

Just a quick update — I've refactored the CodeBuddy/Claude settings.json handling to eliminate the duplication I introduced earlier. The shared logic is now extracted into:

  • patch_settings_json_at(agent_dir, ...) — generic version of patch_settings_json_command(), accepting an explicit agent config directory
  • remove_hooks_from_settings_at(path, hook_commands, ctx) — generic version of remove_hook_from_settings(), accepting a path and command list
  • remove_matching_hooks_from_json(root, hook_commands) — generic version of remove_hook_from_json(), accepting a command list instead of hardcoding CLAUDE_HOOK_COMMAND

CodeBuddy support is now fully wired up and production-ready:

  • rtk hook codebuddy — PreToolUse hook (delegates to process_claude_payload, zero protocol duplication)
  • rtk init -g --agent codebuddy — installs hook + writes ~/.codebuddy/CODEBUDDY.md
  • rtk init -g --agent codebuddy --uninstall — clean removal

All 1996 tests pass. The refactoring makes it straightforward to add future agents that share the same settings.json hook format — just call the shared helpers with a different directory path.

I'd love to get this merged so CodeBuddy Code users can benefit from RTK's token savings. Happy to address any feedback. Thanks for reviewing!

@smilexiaoming

Copy link
Copy Markdown

When will it be released?

@SeeYangZhi SeeYangZhi mentioned this pull request Jun 10, 2026
7 of 10 tasks
@studyzy

studyzy commented Jun 17, 2026

Copy link
Copy Markdown
Author

Hi @FlorianBruniaux @pszymkowiak — gentle ping on this PR. It has been open for about a month and all 1996 tests pass with zero conflicts.

Just a bit of context on why this matters: I am actively working on adding Headroom support for CodeBuddy Code, and that project integrates RTK as its token-optimization layer. Having this PR merged would make the Headroom + RTK + CodeBuddy Code stack work seamlessly out of the box, which benefits both projects.

Quick recap of the change

  • Adds rtk hook codebuddy — a PreToolUse hook for CodeBuddy Code
  • Zero new protocol code: CodeBuddy Code uses the identical PreToolUse JSON format as Claude Code, so it delegates directly to the existing process_claude_payload logic
  • rtk init -g --agent codebuddy — one-command setup
  • I also refactored the shared settings.json helpers (patch_settings_json_at, remove_hooks_from_settings_at) so there is no duplication — just a thin agent-specific wrapper

Scope

  • New: ~120 lines in src/hooks/ + constants
  • Refactored: extracted generic settings.json helpers that benefit future agent integrations
  • Docs: hooks/codebuddy/rtk-awareness.md + hooks/codebuddy/README.md
  • Tests: all 1996 existing tests pass, no regressions

Would really appreciate a review when you have a moment. Happy to address any feedback or make adjustments. Thanks!

@studyzy

studyzy commented Jun 22, 2026

Copy link
Copy Markdown
Author

Hi @aeppling @KuSh — gentle ping on this PR.

I noticed you two have been actively merging PRs this past week (e.g. #2514, #2465, #2416, #2406, #2394, #2294). Would you mind taking a look at this one when you have a moment?

Quick recap

  • Adds rtk hook codebuddy — a PreToolUse hook for CodeBuddy Code
  • Zero new protocol code: CodeBuddy Code uses the same PreToolUse JSON format as Claude Code, so it delegates directly to process_claude_payload
  • rtk init -g --agent codebuddy — one-command setup
  • Refactored shared settings.json helpers (patch_settings_json_at, remove_hooks_from_settings_at) to avoid duplication
  • All 1996 tests pass, no conflicts, CLA signed

This has been open since May 19. Happy to address any feedback or make changes. Thanks!

@KuSh KuSh left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hi, thanks for the PR. A few changes are needed before we can consider merging this.

I’m not familiar with CodeBuddy Code, and while we plan to support the main tools, we don’t necessarily want to add every niche one. Especially since this appears to be compatible with Claude, it seems a symlink to the .claude directory might be all that’s needed.

Could you share a bit more context on why CodeBuddy Code should be integrated here, such as what unique needs it covers beyond Claude compatibility, and how significant its user base or adoption is?

Comment thread docs/guide/getting-started/supported-agents.md Outdated
Comment thread hooks/codebuddy/README.md Outdated
Comment thread hooks/codebuddy/rtk-awareness.md Outdated
Comment thread README.md Outdated
Comment thread src/main.rs
Comment thread src/hooks/init.rs
Comment thread src/hooks/init.rs Outdated
Comment thread src/hooks/init.rs Outdated
Comment thread src/hooks/init.rs Outdated
Comment thread src/hooks/init.rs Outdated
studyzy added a commit to studyzy/rtk that referenced this pull request Jun 29, 2026
- Delegate run_codebuddy to run_claude (zero protocol duplication)
- Switch remove_hooks_from_settings_at to single &str (YAGNI per reviewer)
- Simplify Skip-mode match to unified print_manual_instructions on Skipped|Declined
- Restore Claude-specific output (backup hint + Restart) lost during refactor
- Fix stale doc comment on patch_settings_json_at (relationship reversed)
- Extract uninstall_codebuddy_at for testability + add 3 unit tests
- Simplify resolve_codebuddy_dir match to ?
- Merge Patched|WouldPatch no-op arms; gate Restart print on AlreadyPresent
- Extract patch_mode_from_flags helper (dedup gemini/codebuddy/default)
- Move CodeBuddy entries to list ends per reviewer nitpick
- Add protocol-stability note to hooks/codebuddy/README.md
- Shared rtk_awareness_for_agent template; drop codebuddy/rtk-awareness.md

All 1999 tests pass.
@studyzy

studyzy commented Jun 29, 2026

Copy link
Copy Markdown
Author

Hi @KuSh, thanks for the thorough review — all feedback addressed in the latest push (commit ee31ba6). All 1999 tests pass.

On the top-level question: why CodeBuddy Code, not a symlink

CodeBuddy Code is an independent product (hosted at cnb.cool/codebuddy/codebuddy-code, built by Tencent) — it is not a Claude Code fork or wrapper. A symlink from ~/.codebuddy to ~/.claude would conflate two separate products and:

  • Pollute the user's Claude Code config with CodeBuddy-specific files (CODEBUDDY.md vs CLAUDE.md) and vice versa.
  • Break isolation: uninstalling one would corrupt the other's state.
  • Assume CodeBuddy reads .claude/ at all, which it does not — it ships its own binary, settings loader, and config directory layout.
  • Couple RTK's CodeBuddy support to Claude Code's config schema evolution, which is exactly the protocol-drift risk you raised on hooks/codebuddy/README.md.

The only thing CodeBuddy Code shares with Claude Code is the PreToolUse JSON payload shape (tool_name + tool_input.command), which is a small stable subset. RTK treats them as separate integrations with independent config dirs, hook commands (rtk hook codebuddy vs rtk hook claude), and uninstall paths. I added a Protocol stability section to hooks/codebuddy/README.md making this explicit.

If you'd like more context on adoption: CodeBuddy Code is the AI coding agent bundled with CodeBuddy (Tencent's AI dev suite). I'm actively integrating RTK as the token-optimization layer for CodeBuddy Code via Headroom, so this PR is what makes that stack work out of the box.

Inline comments

File Change
src/hooks/hook_cmd.rs run_codebuddy() now delegates to run_claude() — body collapsed to a one-liner.
src/hooks/init.rs (remove_hooks_from_settings_at / remove_matching_hooks_from_json) Switched from &[&str] to a single &str — every call site passed one element, no need for the slice.
src/hooks/init.rs (patch_settings_json_command) Replaced the match mode { Skip => ..., other => other } with a single post-call if matches!(result, Skipped | Declined) that prints manual instructions once.
src/hooks/init.rs (patch_settings_json_command) Restored the Claude-specific success output (backup hint + Restart Claude Code [and OpenCode]) that was lost when the body moved into the shared helper.
src/hooks/init.rs (patch_settings_json_at doc comment) Rewrote — the old "Mirrors patch_settings_json_command" was backwards (Claude is now the wrapper, this is the core helper).
src/hooks/init.rs (uninstall_codebuddy) Extracted pure filesystem logic into uninstall_codebuddy_at(codebuddy_dir, ctx) -> Result<Vec<String>> so it is unit-testable; added 3 tests (removes md+hook, idempotent when nothing installed, preserves third-party hooks).
src/hooks/init.rs (uninstall_codebuddy) Replaced the match resolve_codebuddy_dir() { Ok(d)=>d, Err _=> println + return } with ? — HOME resolution failure now propagates as a hard error instead of being silently swallowed.
src/hooks/init.rs (run_codebuddy_mode result match) Merged `Patched
src/main.rs Extracted patch_mode_from_flags(auto_patch, no_patch) into hooks::init and used it in all three call sites (gemini, codebuddy, default) — removes the duplicated if auto_patch { Auto } else if no_patch { Skip } else { Ask } block.
docs/guide/getting-started/supported-agents.md + README.md Moved the CodeBuddy row to the end of each list (kept tier grouping intact rather than a full alphabetical sort, since the table is grouped by integration tier — happy to do a strict alphabetical sort instead if you prefer).
hooks/codebuddy/README.md Added a Protocol stability section explaining that CodeBuddy Code is independent and that RTK tracks its protocol separately from Claude Code.
hooks/codebuddy/rtk-awareness.md Deleted; the per-agent copy was identical to hooks/claude/rtk-awareness.md except for the agent name and trailing md filename. Replaced with a shared rtk_awareness_for_agent(agent_name, md_filename) helper that generates the content from the Claude template at install time. hooks/codebuddy/README.md updated to reflect the new flow.

Happy to adjust anything else.

@KuSh KuSh left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the changes! Unfortunately, there’s one big blocker: CodeBuddy handles exit codes differently from Claude, so we’ll need to adapt the preToolUse shell to account for that. Otherwise LGTM!

Comment thread hooks/codebuddy/README.md Outdated
@studyzy

studyzy commented Jun 29, 2026

Copy link
Copy Markdown
Author

Thanks for catching the exit-code/protocol blocker (@KuSh). I dug into it and the root cause is a protocol field-name mismatch rather than exit codes themselves: CodeBuddy's PreToolUse expects modifiedInput, while run_claude emits updatedInput. Delegating to run_claude (the earlier suggestion in the inline review) silently dropped every rewrite under CodeBuddy.

Changes in the latest push (run_codebuddy now has a dedicated path):

  1. Deny rules actually block. Previously the deny branch returned Ok(()) with empty stdout, which CodeBuddy interprets as "allow". It now emits permissionDecision: "deny" with a reason. Without this, RTK deny rules were non-functional under CodeBuddy.
  2. Ask/Default verdicts map to "ask". The dedicated path previously hard-coded "allow", bypassing RTK-configured ask rules. It now matches the existing VSCode/Claude path (Allow => "allow", else "ask").
  3. Protocol field. Uses modifiedInput (CodeBuddy) instead of updatedInput (Claude).
  4. Dropped the misleading rtk rewrite exit-code comment — run_claude never called rtk rewrite either, so that rationale was a red herring; the real reason is the field-name difference above.
  5. Replaced the "PreToolUse" string literal with the PRE_TOOL_USE_KEY constant for consistency with process_claude_payload.

On the earlier inline suggestion to collapse run_codebuddy into run_claude(): that was posted before the protocol difference was identified, and the final review (#pullrequestreview-4589492705) confirmed adaptation is needed, so I kept them separate.

Verified locally: cargo test (1999 passed), cargo clippy --all-targets -- -D warnings (clean), cargo fmt --all -- --check (clean).

Regarding the updatedInput vs modifiedInput divergence and the permissionDecision semantics, I referenced the local CodeBuddy docs at <dist>/docs/en/cli/hooks.md. If the protocol evolves further, this path may need revisiting.

@KuSh KuSh left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks. One last minor change is needed

Comment thread src/hooks/hook_cmd.rs Outdated
@KuSh KuSh self-assigned this Jun 30, 2026
@studyzy

studyzy commented Jul 1, 2026

Copy link
Copy Markdown
Author

All code changes have been updated as requested. Ready for merge. Thanks for the review!

@studyzy

studyzy commented Jul 7, 2026

Copy link
Copy Markdown
Author

Hi @KuSh, thanks for the thorough review on this PR. All your feedback has been addressed:

  • Switched from inline RTK awareness content in CODEBUDDY.md to the same @RTK.md reference pattern used by CLAUDE.md, so user content is preserved and not overwritten
  • Fixed the exit code handling — CodeBuddy's PreToolUse protocol now emits correct modifiedInput (not updatedInput) and proper permissionDecision values (deny/ask/allow)
  • Dropped the 'Code' suffix everywhere, updated the official URL to https://www.codebuddy.ai
  • Added nosemgrep annotations, protocol-stability notes, and unit tests for uninstall
  • Deduplicated Claude/CodeBuddy settings.json hook logic via shared helpers

All 2448 tests pass (including the 3 new CodeBuddy unit tests). I believe this is ready for another review — would you be able to take a look? Thanks!

@xuyisheng

Copy link
Copy Markdown

@TaKO8Ki pls merge this PR

@TaKO8Ki

TaKO8Ki commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Could you please resolve the conflicts? @studyzy

@TaKO8Ki
TaKO8Ki self-requested a review July 29, 2026 06:18
studyzy added a commit to studyzy/rtk that referenced this pull request Jul 29, 2026
Squashed from 9 commits on fork develop branch:
- feat: add CodeBuddy Code support
- refactor: deduplicate Claude/CodeBuddy settings.json hook logic
- refactor: address PR rtk-ai#1967 review feedback
- docs: rename CodeBuddy Code -> CodeBuddy and update official URL
- fix(codebuddy): emit correct protocol fields and deny decision
- fix(codebuddy): differentiate audit_log action for allow vs ask
- fix(codebuddy): add nosemgrep annotation for filesystem-deletion
- fix: make CODEBUDDY.md use @RTK.md reference pattern
- feat(codebuddy): use host permission settings and preserve tool_input
- fix(codebuddy): emit updatedInput instead of modifiedInput in PreToolUse

Rebased onto offical/develop (50 new upstream commits integrated).
Conflicts resolved: both Droid (upstream) and CodeBuddy (fork) integrations kept.
@studyzy

studyzy commented Jul 29, 2026

Copy link
Copy Markdown
Author

Conflicts resolved and force-pushed. Verified locally: cargo test (402 hooks tests passed), cargo check clean.

Key changes in the latest push:

  1. Dual field output. run_codebuddy now emits BOTH updatedInput (Claude Code) and modifiedInput (CodeBuddy) so the same hook output works with both agents — CodeBuddy reads modifiedInput, Claude Code reads updatedInput.
  2. Top-level continue: true + permissionDecision: "allow". CodeBuddy requires both for the rewrite to apply. Also sets permissionDecision inside hookSpecificOutput.
  3. Matcher widened to Bash|execute_command. Catches both CLI mode (Bash) and IDE mode (execute_command).
  4. RTK.md awareness content customized for CodeBuddy via rtk_awareness_for_agent("CodeBuddy", "CODEBUDDY.md") — no more "Claude Code" / "CLAUDE.md" leakage.
  5. hook_already_present cross-agent fix. is_claude_hook_command now only matches when hook_command == CLAUDE_HOOK_COMMAND, so a stray rtk hook claude entry in ~/.codebuddy/settings.json no longer falsely suppresses the CodeBuddy hook install.
  6. README example updated to reflect the actual JSON shape (dual fields + top-level continue/permissionDecision).

Ready for review and merge. Thanks!

@KuSh KuSh left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Not at home, so hard to review on phone but here are some preliminary feedback of your new proposition

Comment thread hooks/codebuddy/README.md Outdated
Comment thread hooks/codebuddy/README.md Outdated
Comment thread src/hooks/constants.rs
Comment thread src/hooks/hook_cmd.rs Outdated
Comment thread src/hooks/hook_cmd.rs Outdated
Comment thread src/hooks/init.rs
@studyzy

studyzy commented Aug 6, 2026

Copy link
Copy Markdown
Author

Thanks for the review, @KuSh! All feedback addressed in the latest push (commit 72b3611).

Summary of changes:

  1. Removed redundant updatedInput — CodeBuddy only reads modifiedInput, so the hook now emits only that field.
  2. Unified ask→allow — Tested on actual CodeBuddy IDE: when permissionDecision is "ask", CodeBuddy pops a confirmation but ignores modifiedInput and runs the original command. Only "allow" applies the rewrite. Since RTK has already vetted the command (dangerous constructs are Deferred), collapsing AskRewrite into allow is safe and ensures rewrites always take effect. The prompt hook approach would add complexity without any security gain.
  3. Clarified uninstall logic — Added a comment confirming non-Claude agents only match their own hook command (the absolute-path branch is gated on hook_command == CLAUDE_HOOK_COMMAND).
  4. Claude matcher constant — This is orthogonal to the CodeBuddy PR (touches the Claude init path). Happy to submit a separate refactor PR if desired.

All 2578 tests pass.

@KuSh KuSh left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

My main concern is the auto-execute bypass. I couldn’t find anything referencing the issue, and I’d prefer to know it’s tracked and temporary. We could then hold off on this PR until it’s fixed.

In the meantime, there are still a few bugs lurking, along with some code duplication and minor suggestions. It would also be great if you could rebase, since you have conflicts right now

Comment thread src/hooks/hook_cmd.rs Outdated
Comment thread src/hooks/permissions.rs Outdated
Comment thread src/hooks/init.rs Outdated
Comment thread src/hooks/hook_cmd.rs Outdated
Comment thread src/hooks/permissions.rs
Comment thread src/hooks/init.rs Outdated
@studyzy
studyzy force-pushed the develop branch 2 times, most recently from 86d729c to d37c128 Compare August 11, 2026 09:22

@studyzy studyzy left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks for the thorough review. I've addressed the auto-execute bypass concern — this was the main blocker.

Auto-execute bypass is resolved. The earlier version collapsed AskRewrite into "allow" as a workaround for a CodeBuddy IDE bug where permissionDecision: "ask" popped a confirmation but ignored the rewritten command. That bypass is now removed: AskRewrite maps back to permissionDecision: "ask" and forwards the rewrite via updatedInput, exactly like the allow path. The ask/allow distinction is honored again.

On the IDE bug itself: it's tracked upstream with CodeBuddy and is scheduled to be fixed in their next release. The workaround (and the revert path) is documented in hooks/codebuddy/README.md and src/hooks/hook_cmd.rs with WORKAROUND/TODO labels, so once the fix lands we simply restore the mapping — no protocol change needed on our side.

Rebased. The branch is now a single clean commit on top of the latest rtk-ai/develop, and the PR is mergeable with no conflicts.

Code duplication / minor suggestions. I addressed the ones that were in scope:

  • build_updated_input is now shared by the Claude, CodeBuddy, and Droid paths (no duplication).
  • The manual-install snippet and auto-install matcher were aligned to Bash|execute_command.
  • find_project_root now takes the caller's own agent_dir, so each host resolves its config independently — a sibling host's dir in a subdirectory no longer affects another host's rules.

Two items I deliberately kept out of scope to keep the change minimal, happy to follow up on either:

  • load_permission_rules (Claude) and load_codebuddy_rules share shape but are kept separate to avoid touching the long-stable Claude path; I can extract a shared helper if you'd like.
  • Consolidating remove_rtk_reference_from_agents and remove_rtk_refs_from_md into one host-agnostic helper (the CodeBuddy path already uses the _from_md helper, which uses atomic writes and exact matching).

Happy to adjust anything else. Thanks again for the careful review!

Closes rtk-ai#1966

Adds first-class support for CodeBuddy (Tencent Cloud AI Code Editor)
via its PreToolUse hook protocol, which mirrors Claude Code's
`tool_name` + `tool_input.command` JSON shape.

Changes:
- `rtk hook codebuddy` — PreToolUse hook processor (reads JSON from stdin,
  rewrites `tool_input.command`)
- `rtk init -g --agent codebuddy` — installs the hook into
  `~/.codebuddy/settings.json` and writes `~/.codebuddy/CODEBUDDY.md`
- `rtk init -g --agent codebuddy --uninstall` — removes all RTK artifacts
- `Host::CodeBuddy` permission rules, read from `.codebuddy/settings.json`
  and `.codebuddy/settings.local.json`

The hook emits `updatedInput` (CodeBuddy upstream converged on this field,
same as Claude Code) and mirrors RTK's verdict: AllowRewrite -> "allow",
AskRewrite -> "ask". Shared settings.json helpers (`patch_settings_json_at`,
`remove_hooks_from_settings_at`, `rtk_awareness_for_agent`) are reused so
no Claude/CodeBuddy logic is duplicated.
@studyzy

studyzy commented Aug 11, 2026

Copy link
Copy Markdown
Author

Quick clarification on two protocol points, based on direct communication with the CodeBuddy team:

  1. updatedInput is the standard, modifiedInput is deprecated. I confirmed with the CodeBuddy maintainers that updatedInput is the intended field, and modifiedInput is a legacy design that is being phased out. Their docs are not yet updated to reflect this, but they plan to update them. This is why the hook emits updatedInput — it matches the forward-looking, standard contract.

  2. The ask-decision IDE bug is confirmed and will be fixed. The issue where CodeBuddy IDE pops a confirmation on permissionDecision: "ask" but ignores the rewritten command has been reported to the CodeBuddy team, and they have confirmed it will be fixed in their next release. Until then, the workaround and revert path remain documented in hooks/codebuddy/README.md and src/hooks/hook_cmd.rs.

Given both points, the current implementation (emit updatedInput, honor the ask/allow distinction) is the right baseline: once the IDE fix lands, no change is needed on RTK's side.

@KuSh KuSh left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM, except for a few nitpicks. Thanks for all your work and dedication. I’ll hold off on merging until you can confirm the ask bug is fixed in CodeBuddy though

Comment thread src/hooks/init.rs Outdated
Comment thread src/main.rs
} else {
hooks::init::PatchMode::Ask
};
let patch_mode = hooks::init::patch_mode_from_flags(auto_patch, no_patch);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nitpick: patch_mode_from_flags seems to be used only here, so it doesn’t need to be public in init

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

patch_mode_from_flags lives in src/hooks/init.rs but is called from src/main.rs in three places (the CodeBuddy branch, the default Claude/Copilot branch, and another agent branch), so it must remain pub — it's a cross-module API, not an internal-only helper. No change needed here.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Call count wasn't the objection — it's which module calls it.

patch_mode_from_flags is defined in src/hooks/init.rs:98, but init.rs itself never calls it. All three call sites are in src/main.rs (2046, 2087, 2096). So the pub exists purely to export a helper out of a module that has no use for it.

It's also introduced by this PR — it isn't present at the merge-base (9936b2b) — so it's not pre-existing debt.

And the dedup is incomplete either way: the Vibe branch at src/main.rs:2077-2083 still spells out the exact if/else the helper was extracted to replace, four lines above a patch_mode_from_flags(...) call:

} else if agent == Some(AgentTarget::Vibe) {
    let patch_mode = if auto_patch {
        hooks::init::PatchMode::Auto
    } else if no_patch {
        hooks::init::PatchMode::Skip
    } else {
        hooks::init::PatchMode::Ask
    };

Suggestion: move it into main.rs as a private fn patch_mode_from_flags and use it for the Vibe branch too. That drops a pub item from init's API surface and removes the leftover inline copy in one go — main.rs already names PatchMode::{Auto,Skip,Ask} directly, so nothing new needs exporting.

Comment thread src/hooks/init.rs Outdated
Comment thread src/hooks/hook_cmd.rs Outdated
Comment thread src/hooks/permissions.rs
- Generalize patch_claude_md into patch_md_with_ref so verbose/dry-run
  messages no longer hardcode CLAUDE.md when operating on CODEBUDDY.md
- Interpolate CODEBUDDY_MATCHER / CODEBUDDY_HOOK_COMMAND in the manual
  install snippet instead of hardcoding the strings
- Validate tool_name in run_codebuddy via codebuddy_execute_command,
  mirroring droid_execute_command
- Extract load_rules_from_paths shared by load_permission_rules (Claude)
  and load_codebuddy_rules, which differ only in resolved settings paths
@studyzy

studyzy commented Aug 17, 2026

Copy link
Copy Markdown
Author

@KuSh thanks for the thorough review — all remaining nits are addressed in the latest push (1e23b66), and the ask blocker is now resolved.

Confirming the ask bug is fixed

I've tested on the latest CodeBuddy IDE 4.11.1, where the upstream fix has landed: permissionDecision: "ask" now correctly applies updatedInput instead of running the original command. The ask/allow distinction is honored end-to-end with no workaround on RTK's side.

Changes in this push

  1. patch_claude_md generalized → patch_md_with_ref(path, md_name, ctx). The verbose/dry-run messages no longer hardcode CLAUDE.md when operating on CODEBUDDY.md (your nitpick on init.rs:5238). patch_claude_md is now a thin wrapper passing CLAUDE_MD.
  2. Manual-install snippet interpolates constants. The Declined | Skipped message now interpolates CODEBUDDY_MATCHER and CODEBUDDY_HOOK_COMMAND instead of duplicating "Bash|execute_command" / "rtk hook codebuddy" (your nitpick on init.rs:5271).
  3. tool_name validated in run_codebuddy. Added codebuddy_execute_command, mirroring droid_execute_command: only Bash (CLI) / execute_command (IDE) / missing tool_name proceed (your nitpick on hook_cmd.rs:708).
  4. Extracted load_rules_from_paths(paths). Shared by load_permission_rules (Claude) and load_codebuddy_rules, which now differ only in the settings-path function they call (your nitpick on permissions.rs:226).

Verified locally: cargo test (all pass), cargo clippy --all-targets -- -D warnings (clean), cargo fmt --all -- --check (clean). Ready for another look — thanks!

@KuSh KuSh left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-reviewed at 1e23b66. First: I verified all four fixes from your last push are real, not just described — patch_md_with_ref interpolates md_name in all five messages, the manual-install snippet uses the constants, codebuddy_execute_command faithfully mirrors droid_execute_command, and load_rules_from_paths collapses the duplication. cargo fmt --check, cargo clippy --all-targets -- -D warnings and cargo test --all (2662 tests) are all green on the head. Thanks for the thorough turnaround.

The following are new findings from a fresh pass over the whole PR, not leftovers from the previous round. One is significant (the ask verdict on default), the rest are polish. Leaving this as a comment rather than blocking — your call on scope.

Separately, the patch_mode_from_flags thread is still open; I replied there with why the visibility point stands.

Comment thread src/hooks/hook_cmd.rs
Comment on lines +760 to +763
HookDecision::AskRewrite(r) => {
audit_log("ask", cmd, &r);
("ask", "RTK auto-rewrite", Some(r))
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This makes CodeBuddy prompt on every rewritten command.

decide_from_verdict maps both an explicit Ask rule and the Default "no rule matched" verdict to HookDecision::AskRewrite:

// hook_cmd.rs:266-270
match get_rewritten(cmd) {
    Some(r) if verdict == PermissionVerdict::Allow => HookDecision::AllowRewrite(r),
    Some(r) => HookDecision::AskRewrite(r),   // <- Default lands here too
    None => HookDecision::Defer,
}

So this arm asserts permissionDecision: "ask" for the default case. And the default case is the normal case: patch_settings_json_at writes a settings.json containing only a hooks key — no permissions block — so after a fresh rtk init -g --agent codebuddy, load_codebuddy_rules() returns empty vectors and every command RTK rewrites gets "ask". The agent runs git status, RTK rewrites it, CodeBuddy shows a blocking approval dialog. Every time.

This is the regression already documented in this file at hook_cmd.rs:288-295:

permissionDecision: "allow" is only ever asserted for an explicit, user-configured Allow rule. Every other rewrite (Default verdict or an explicit Ask rule) omits the field entirely, leaving the host's own native prompt/allowlist flow in control — see #3037, where asserting "ask" here made Copilot CLI 1.0.66+ force a blocking dialog with no "remember" option on every rewritten command.

Both sibling paths follow that rule and insert the field only when the verdict is an explicit Allow — process_claude_payload (:598/:613) and vscode_response_from_decision (:304/:314). CodeBuddy is currently the only host that asserts "ask".

I realise honouring the ask/allow distinction was deliberate, and now that the IDE bug is fixed the rewrite does survive the prompt. But #3037 was about prompt fatigue, not correctness — and that argument applies here unchanged.

Two ways out:

  • Omit permissionDecision unless the verdict is an explicit Allow (mirrors Claude/VS Code exactly), or
  • Split Default from Ask in HookDecision so only a user-configured Ask rule produces "ask".

The second preserves your intent while keeping the default install quiet.

Comment thread src/hooks/hook_cmd.rs
Comment on lines +727 to +731
fn codebuddy_execute_command(v: &Value) -> Option<&str> {
let tool_name = v.get("tool_name").and_then(|t| t.as_str()).unwrap_or("");
if !matches!(tool_name, "Bash" | "execute_command" | "") {
return None;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This gating has no test coverage.

The function it mirrors is tested for exactly this behaviour:

  • test_droid_ignores_non_execute_tool (hook_cmd.rs:2194)
  • test_droid_bash_tool_name_accepted_defensively (hook_cmd.rs:2205)

The four CodeBuddy tests (:1677-:1728) all construct their payload with codebuddy_payload() and call codebuddy_response_from_decision directly, so none of them ever reach codebuddy_execute_command. If a later refactor drops or inverts the matches! guard, nothing fails and RTK silently starts rewriting commands for non-shell tools again.

Two small tests mirroring the Droid pair would close it — a payload with tool_name: "Read" returning None, and one with execute_command / missing tool_name returning the command.

Comment thread src/hooks/init.rs
Comment on lines +5258 to +5261
if dry_run {
print_dry_run_footer();
} else {
println!("\nCodeBuddy hook installed (global).\n");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reports success even when nothing was installed.

This println! is unconditional and runs before the match result below, so it fires on PatchResult::Declined and PatchResult::Skipped too.

Answer n at the consent prompt (or pass --no-patch) and the output reads:

CodeBuddy hook installed (global).

  To add manually, patch ~/.codebuddy/settings.json:
  {"hooks": {"PreToolUse": [...]}}

Two contradictory statements, success first. Anyone skimming believes it worked, while settings.json was never touched and RTK stays inert on every subsequent command.

The Claude path gates the equivalent text on the result (init.rs:964):

if result == PatchResult::Patched {
    ...
}

Same gate here would fix it.

Comment thread src/hooks/init.rs
Comment on lines +5270 to +5273
PatchResult::Patched | PatchResult::WouldPatch => {}
PatchResult::AlreadyPresent => {
println!(" settings.json: hook already present");
println!(" Restart CodeBuddy. Test with: git status\n");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The restart notice is on the wrong arm.

AlreadyPresent — the case where nothing changed — tells the user to restart. Patched — the case where the hook was just written and a restart is exactly what's needed — is an empty block and prints nothing.

Net effect on a first install: RTK writes the hook, says nothing about restarting, the user doesn't restart, the hook never loads, and RTK appears broken. Run it a second time and then you're told to restart.

The Claude path attaches the notice to Patched (init.rs:970-976); worth matching.

Comment thread src/hooks/init.rs
Comment on lines +43 to +47
fn rtk_awareness_for_agent(agent_name: &str, md_filename: &str) -> String {
RTK_SLIM
.replace("Claude Code", agent_name)
.replace("CLAUDE.md", md_filename)
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This blind replace puts a false instruction into ~/.codebuddy/RTK.md.

.replace("Claude Code", agent_name) also rewrites line 10 of hooks/claude/rtk-awareness.md:

rtk discover  # Analyze Claude Code history for missed opportunities

which becomes "Analyze CodeBuddy history for missed opportunities". But rtk discover can't do that — DiscoverProvider::projects_dir (src/discover/provider.rs:46-48) resolves only claude_dir.join("projects"), i.e. ~/.claude/projects. A CodeBuddy-only user follows the instructions RTK just installed for them and gets an empty result with no explanation.

Simplest fix is to drop that line from the non-Claude variant, or leave rtk discover's description untouched by the substitution.

Comment thread src/hooks/permissions.rs
Comment on lines +384 to 391
fn find_project_root(agent_dir: &str) -> Option<PathBuf> {
// Fast path: walk up CWD looking for the agent's config dir — no subprocess needed.
let mut dir = std::env::current_dir().ok()?;
loop {
if dir.join(CLAUDE_DIR).exists() {
if dir.join(agent_dir).exists() {
return Some(dir);
}
if !dir.pop() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Parameterising this changes behaviour for Gemini and Droid, not just CodeBuddy.

At the merge-base this walked up looking for .claude/ unconditionally. This PR also repoints the existing callers — gemini_settings() now passes GEMINI_DIR, droid_settings_scopes() passes DROID_DIR.

.claude/ is present in most projects, so the fast walk almost always hit before. .gemini/ / .droid/ often aren't, so those paths now fall through to the git rev-parse --show-toplevel fallback below — a subprocess spawn per intercepted Bash command, on the hook hot path, against the repo's documented <10 ms startup budget. It only bites when CWD is outside $HOME (inside $HOME the walk finds ~/.gemini), so the blast radius is narrow, but it's a real change to paths this PR isn't otherwise touching.

Caching the resolved root, or keeping .claude/ as a secondary probe before falling back to git, would avoid it.

For the record on the neighbouring concern: the double-push of the two home paths that get_codebuddy_settings_paths can produce when the walk resolves the root to $HOME is not a new bug — get_settings_paths (permissions.rs:156) has had the identical shape for Claude all along. It costs a redundant read and duplicate rules, never a wrong verdict.

@pszymkowiak

Copy link
Copy Markdown
Collaborator

This narrows a match used by the existing Claude uninstall path, not just new CodeBuddy code — worth flagging as a regression.

remove_rtk_refs_from_md (new shared helper, init.rs) replaces the old inline CLAUDE.md cleanup with:

.filter(|line| {
    let trimmed = line.trim();
    !refs.contains(&trimmed)
})

The old code it replaces used a prefix match:

.filter(|line| !line.trim().starts_with(RTK_MD_REF))

starts_with strips any line beginning with @RTK.md, regardless of trailing text. refs.contains(&trimmed) only strips a line that's exactly @RTK.md after trimming.

Repro: a CLAUDE.md line like @RTK.md — see this for token-saving instructions gets removed by rtk init --uninstall -g on develop, but survives on this branch — the exact-match filter leaves it behind. Since this helper is called from the pre-existing Claude uninstall step (not just the new CodeBuddy one), this is a regression in already-working behavior, not just a gap in the new code.

`remove_rtk_refs_from_md` replaced the previous prefix match
(`starts_with(RTK_MD_REF)`) with an exact line match, so a reference line
carrying a trailing comment (e.g. `@RTK.md — see below`) was left behind on
uninstall. Restore substring detection + prefix stripping and add regression
tests.
@studyzy

studyzy commented Aug 28, 2026

Copy link
Copy Markdown
Author

@pszymkowiak good catch — you're right, that's a regression in the existing Claude uninstall path, not just a gap in the new CodeBuddy code.

Fixed in the latest push (f430b57):

  • remove_rtk_refs_from_md restores the original semantics: substring detection (refs.iter().any(|r| working_content.contains(r))) plus prefix stripping (trimmed.starts_with(r)). A line like @RTK.md — see this for token-saving instructions is removed again, matching pre-PR develop behavior.
  • has_rtk_reference (exact line match) is no longer the gate for this path, so the case where the only reference line carries a trailing comment also triggers cleanup.
  • Added two regression tests: test_remove_rtk_refs_from_md_strips_trailing_comment_reference and test_remove_rtk_refs_from_md_strips_mixed_reference_lines.

Verified: cargo test --bin rtk (2587 passed, 0 failed), cargo clippy --all-targets -- -D warnings clean, cargo fmt --all -- --check clean.

I deliberately left remove_rtk_reference_from_agents (AGENTS.md) unchanged — it was already exact-match on develop, so no behavior change there.

@yeahjack

Copy link
Copy Markdown
Contributor

@studyzy @KuSh I would be happy to help move this PR forward after #4151 lands. I verified updatedInput rewriting on CodeBuddy CLI 2.151.0 and can help rebase onto the shared hook helpers, address the remaining default/ask behavior, and add regression tests. Would you be open to a follow-up patch on your branch?

@studyzy

studyzy commented Sep 21, 2026

Copy link
Copy Markdown
Author

Thanks @yeahjack! Yes, absolutely open to a follow-up patch on this branch. Once #4151 lands, I'll coordinate the rebase onto the shared hook helpers, and your help with the remaining default/ask behavior and regression tests would be very welcome — especially given your verified testing on CodeBuddy CLI 2.151.0.

Happy to add you as a collaborator on my fork so you can push follow-up patches directly; will follow up on that. Thanks for offering to help move this forward!

@yeahjack

Copy link
Copy Markdown
Contributor

Thanks @studyzy! That sounds good. I’ll focus on the default/ask behavior and regression tests, and leave the rebase coordination to you once #4151 lands. Happy to push follow-up commits after the collaborator invite comes through.

@yeahjack

Copy link
Copy Markdown
Contributor

@studyzy #4151 has now merged, so the shared hook helpers are ready. Let me know once you have rebased this branch, and I can work on the default/ask behavior and regression tests as discussed. Please send the collaborator invite when you have a chance. Thanks!

@yeahjack yeahjack mentioned this pull request Sep 25, 2026
4 tasks done
@yeahjack

yeahjack commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

@studyzy A gentle follow-up: could you let me know when the rebase is ready and send the collaborator invite? I can then help with the default/ask behavior and regression tests as discussed. Thanks!

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.

Feature Request: Add support for CodeBuddy (Tencent Cloud AI Code Editor)

8 participants