Skip to content

feat(hooks): add Trae IDE integration - #3008

Merged
KuSh merged 16 commits into
rtk-ai:developfrom
deqingLv:feat/trae-hook-integration
Sep 18, 2026
Merged

KuSh merged 16 commits into
rtk-ai:developfrom
deqingLv:feat/trae-hook-integration

Conversation

@deqingLv

@deqingLv deqingLv commented Jul 15, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Add native Trae IDE integration: rtk init --agent trae installs a PreToolUse hook for RunCommand, and rtk hook trae rewrites supported commands while leaving approval to Trae.

Closes #3234
Closes #1676

Behavior

  • Project install/uninstall manages .trae/hooks.json; global mode manages ~/.trae/hooks.json and existing ~/.trae-cn/hooks.json.
  • Installation and removal identify RTK by command, including rtk.exe and quoted executable paths. Customized matcher/timeout fields do not cause duplicate registration or prevent removal; unrelated hooks and configuration are preserved.
  • Configuration and hook stdin accept UTF-8 BOMs. The response preserves input fields, replaces only command, and omits permissionDecision. Unattestable shell constructs defer without rewriting.
  • Global installation preflights all configurations. If a later write fails, the error identifies the failed target and any targets already updated; retrying does not duplicate completed registrations.
  • The audit-log integration test is Unix-only because Windows Known Folder resolution cannot be redirected by HOME. Rewrite/BOM integration coverage remains cross-platform. Shared audit-log path behavior is left to hook-audit log path uses Linux /tmp on Windows, doesn't honor XDG_DATA_HOME #1681.

Validation

Includes develop at 6d104308c56c0a51250f8a200e5056787128fb65 via merge commit 651441c.

Local macOS validation on September 18, 2026, at ceae215ff7841f5857dabd6295ddc92ba2d7b76d:

  • cargo fmt --all --check
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo test --all-features: 3832 passed, 8 ignored, 0 failed
  • git diff --check
  • bash scripts/check-test-presence.sh upstream/develop
  • Temporary-directory CLI checks: customized matcher/timeout install and uninstall; BOM configuration/input; rtk.exe idempotency/removal; partial-write diagnostics and retry.

Upstream cross-platform CI and maintainer approval remain pending; these local checks do not establish Windows CI success.

@CLAassistant

CLAassistant commented Jul 15, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@deqingLv
deqingLv force-pushed the feat/trae-hook-integration branch from 5900881 to 22f3fe8 Compare July 20, 2026 08:19
@deqingLv

Copy link
Copy Markdown
Contributor Author

@KuSh, could you please help review this PR and approve/run the fork CI when you have a moment?

I have addressed the safety gate, canonical Trae documentation, and audit coverage. The full local gate passes: formatting, clippy with warnings denied, and all tests (2583 passed, 8 ignored, 0 failed).

If everything looks good, I would appreciate your help moving it toward merge. Thank you!

@KuSh KuSh mentioned this pull request Sep 6, 2026
5 tasks done
@deqingLv

Copy link
Copy Markdown
Contributor Author

@KuSh Could you please review this PR and approve the fork CI run when you have a moment?

I have synced with the latest develop (5e0f92c) and pushed merge commit ca24862, resolving the conflicts while retaining the upstream Codex and Oh My Pi integrations. Trae now uses the shared hook decision interface and is registered in rtk hook check; approval remains owned by Trae.

Local validation passes: formatting, strict Clippy, all-feature tests (3799 passed, 8 ignored), release build, test-presence check, and a temporary-directory install/rewrite/uninstall smoke check. The PR description now contains the current validation results. Upstream cross-platform CI is still pending.

Thank you for helping move this toward merge!

@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.

Round 1 of 3 — CHANGES REQUESTED

Claim: install and uninstall a Trae PreToolUse hook for RunCommand (project and global, plus ~/.trae-cn when it exists) and rewrite commands through rtk, leaving approval to Trae.

Scope: accept (frozen at round 1). This is the surviving Trae PR — #1447 was closed as a duplicate of it, #2411 and #2056 are closed too, and nothing else open touches Trae. Registration parity with the Codex and Cursor siblings is complete across decision.rs, permissions.rs and main.rs.

Ran: both binaries built in separate target dirs and hash-checked before trusting any transcript. Project and global install, --show, --uninstall, --dry-run, a second install on top (idempotent), an existing hooks.json full of unrelated user hooks through install → uninstall (round-trips exactly, userSetting and PostToolUse untouched), ~/.trae-cn opt-in, CRLF config, BOM config, a malformed sibling target (preflight holds — nothing written), a read-only target, six malformed PreToolUse payloads, empty and closed stdin, and rtk hook check --agent trae diffed against --agent codex. Gates green on the head and on the head merged onto the latest develop (0a71fcf3, clean merge, 3801 tests). The rewrite this hook installs delivers 46–65% on git status / git log -20, against the 20% floor (CONTRIBUTING.md).

CI @ca24862: never built. Run 34968708266 is action_required; only the CLA check and a skipped check-target are green. I have approved the run — please check it once it finishes.

Blocks merge (1, frozen at round 1)

  1. src/hooks/init.rs:5276 — presence and removal probes gate on fields RTK does not control (2 sites: :5214+:5228 and :5276; both must change). See the inline comment for the transcript and the fix.

Optional — will not hold merge

These are all "the new Trae code hand-rolls a helper that already exists and loses behaviour in the copy". I built and measured each fix rather than guessing, so the before → after is real, but none of them holds the merge.

  1. BOM-blindness, 3 sites — run_trae (hook_cmd.rs), read_trae_hooks_json and remove_trae_hooks_json_paths (init.rs) parse with a raw serde_json::from_str. read_json_file (14 callers) and every sibling handler in hook_cmd.rs go through strip_leading_bom / from_json_str. Given the same UTF-8-BOM'd hooks.json:

    rtk init -g --agent cursor  → exit 0, installed
    rtk init -g --agent trae    → exit 1, "Failed to parse Trae hooks file ... as JSON"
    

    and a BOM'd PreToolUse payload makes the hook skip the rewrite entirely, where rtk hook claude rewrites it. Routing the two init.rs readers through read_json_file fixes the install and uninstall paths and removes the duplication at the same time; run_trae needs strip_leading_bom(&input).trim() like its five siblings.

  2. src/hooks/mod.rs:42 — is_trae_hook_command re-implements is_rtk_hook_command directly above it, minus its rtk.exe arm, so an entry registered with the Windows spelling is invisible to both the idempotence probe and the uninstaller ("nothing to remove", entry left behind, and a reinstall then appends a duplicate). One line: is_rtk_hook_command(command, "trae").

  3. src/hooks/init.rs:5171 — the write loop is not all-or-nothing. With ~/.trae-cn read-only, rtk init -g --agent trae exits 1 naming only .trae-cn, but ~/.trae has already been patched and no message says so. Re-running is idempotent so nothing is lost — the error should just name what it applied. (The docstring's "malformed .trae or .trae-cn configuration cannot cause a partially-applied update" is accurate as written: I checked it, and preflight does cover malformed config. This is the write-failure case, not the malformed one.)

Checked and correct — no need to re-verify

  • process_trae_payload preserves tool_input (clones it, replaces only command; description and timeout survive) and emits nothing on every defer path.
  • Omitting permissionDecision is right, and HookDecision::Deny is unreachable for Host::Trae because its rule vectors are empty — so the missing deny audit entry is not observable.
  • Preflight-before-write against malformed config, backup-before-atomic_write ordering, the results/paths zip alignment, insert_trae_hook_entry not mutating a rejected root, and --dry-run writing nothing.
  • Unrelated hooks and unrelated top-level keys survive install and uninstall byte for byte.
  • Sibling registration parity — nothing a sibling registers is missing for Trae.

Hypotheses built and dropped

  • rtk init --show not reporting Trae — it does not report Codex, Gemini, Droid, Pi or OMP either, and the docs this PR adds never claim it does. Pre-existing, not this PR's job.
  • A permissionDecision or hookSpecificOutput field-loss bug — ruled out by reading the clone and confirming it against the real output.
  • error[E0308]: type mismatch in the test output — a pre-existing compile-fail test, present on unmodified develop.
  • The 60% savings floor in .claude/rules/cli-testing.md — stale (the floor is 20%), and it does not apply to a hook integration in any case.

Overlaps worth knowing about

  • #3878 (refactor(hooks): split init.rs into per-agent submodules, +11295/−11123) will conflict with this PR's +364 in init.rs. My view: #3008 lands first — it is older, far smaller, and #3878 rebases mechanically. Nothing for you to do here.
  • #4028 touches the same hooks/README.md agent counter this PR bumps 11 → 12. Trivial; whichever lands second adjusts.

Your questions

  • "Could you please review this PR and approve the fork CI run when you have a moment?" → Done on both counts: this is the review, and I have approved run 34968708266. Worth knowing that every round so far has been judged on local gates only — CI has never actually built this branch, so please watch that run.

One more thing

The PR closes no issues. #3234 ("Add hook support for Trae IDE (PreToolUse on RunCommand tool)") and #1676 ("[Agent] Add Trae.ai IDE support") are exactly what this implements — adding Closes #3234 and Closes #1676 to the description would close them on merge.

Next

Fix the matcher/timeout gating; ideally take the three optional items in the same push, and add the two Closes lines. Then let CI finish and I will approve.

Rounds: 1/3. Threads: 1 open (the blocking item), 0 resolved this round.

Comment thread src/hooks/init.rs Outdated
@KuSh

KuSh commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator

CI finished — test (windows-latest) failed, and it is this PR's own new test

I approved the run right after posting the review; it is the first time this branch has been built by CI. Everything else is green — doc review, fmt, clippy, test presence, semgrep, Security Scan, test (ubuntu-latest), test (macos-latest). Windows is the single failure, so this joins the blocking set:

test trae_hook_records_successful_rewrite_in_audit_log ... FAILED

panicked at tests\trae_hook_test.rs:70:33:
missing audit log at C:\Users\RUNNER~1\AppData\Local\Temp\.tmp2cEUW4\.local/share/rtk/hook-audit.log:
The system cannot find the path specified. (os error 3)

test result: FAILED. 1 passed; 1 failed

Cause — the test redirects the child with .env("HOME", home) and then reads home/.local/share/rtk/hook-audit.log, but the writer does not read HOME:

  • audit_log_inner (src/hooks/hook_cmd.rs:529) resolves its directory with dirs::home_dir().
  • On Windows, dirs 5.0.1 routes that through dirs-sys 0.4.1 known_folder_profile() → SHGetKnownFolderPath(FOLDERID_Profile) (dirs-sys-0.4.1/src/lib.rs:173). That is a Win32 call which consults no environment variable at all — not HOME, and not USERPROFILE either.

So on Windows the hook writes into the runner's real profile directory while the test looks inside the tempdir, and the assertion can never pass there. trae_hook_defers_unattestable_shell_constructs survives because it only asserts that stdout is empty and never touches the audit log.

Suggested fix: #[cfg(unix)] on trae_hook_records_successful_rewrite_in_audit_log. Adding USERPROFILE to the child env will not help, for the reason above — the audit directory simply is not redirectable from the outside on Windows today, so there is no cross-platform form of this assertion to write. The Trae rewrite itself is already covered on all three platforms by the other integration test and by the in-process unit tests, so nothing is lost by scoping this one to Unix.

Not something to fix here: the reason it is not redirectable is that the writer and the reader disagree — audit_log_inner uses dirs::home_dir() while hook_audit_cmd::default_log_path() (src/hooks/hook_audit_cmd.rs:8) uses std::env::var("HOME") with a /tmp fallback. That is pre-existing, untouched by this PR, and already tracked as #1681 ("hook-audit log path uses Linux /tmp on Windows, doesn't honor XDG_DATA_HOME"). Please do not take it on in this PR.

Updated blocking set, still 2:

  1. src/hooks/init.rs:5276 — the matcher/timeout gating on the presence and removal probes (the inline thread above).
  2. tests/trae_hook_test.rs:70 — this Windows failure.

The three optional items from the review are unchanged and still will not hold the merge. Once both of the above are pushed I will re-run the round against the new head and approve on green CI.

@deqingLv

Copy link
Copy Markdown
Contributor Author

@KuSh The two blocking items and all three optional review items are addressed. Current head: ceae215; the branch includes develop at 6d10430.

  • Matcher/timeout gating: e988098 makes presence and removal command-based. The original inline thread now has the regression details and CLI results.
  • Windows audit test: ceae215 follows your CI comment: the filesystem audit assertion is now #[cfg(unix)]. I removed the interim RTK_AUDIT_DIR writer change from dbecb98 and its documentation, so the shared audit-path behavior is unchanged and hook-audit log path uses Linux /tmp on Windows, doesn't honor XDG_DATA_HOME #1681 remains separate. Rewrite/BOM integration tests still run on all platforms; this does not rely on USERPROFILE.
  • BOM handling: 0184ba2 routes both configuration readers through read_json_file and strips leading BOMs in the native Trae handler. Single/double-BOM regression tests cover install, uninstall and real stdin rewrite output.
  • Windows command spelling: 0184ba2 reuses is_rtk_hook_command; rtk.exe and quoted Windows paths pass idempotent-install/uninstall coverage.
  • Partial writes: 0184ba2 adds error context naming the failed target and targets already updated. A deterministic write-failure regression plus a read-only-directory CLI check verify the message. This reports partial application rather than claiming rollback; retry after fixing permissions is idempotent.

The PR description now reflects the current implementation and validation and includes Closes #3234 / Closes #1676.

Local macOS validation at ceae215: formatting, strict all-target/all-feature Clippy, all-feature tests (3832 passed, 8 ignored, 0 failed), diff whitespace and test-presence checks pass. The CLI reproductions of the review scenarios also pass. I have not verified a new Windows CI run and am not treating local results as cross-platform CI evidence.

Please re-review the current head and approve/run the fork CI if approval is required.

…by command

Matching the RTK command alone settled the round-1 defect, where a matcher or
timeout the user had edited made uninstall report "nothing to remove" and a
reinstall append a duplicate. It also went one step too far in both directions,
and this restores the two distinctions that were lost with it.

Presence now only counts a registration that would run for the tool RTK serves:
a group with no matcher, or one naming RunCommand among its `|`-separated
tools. Anything unrecognised counts as not covering RunCommand, so an install
adds an entry that fires rather than skipping one that never would. Removal
stays matcher-blind, which is what round 1 asked for, but skips an entry whose
explicit type is not `command`: a `prompt` entry is by construction the user's,
and deleting it also took the group and its `matcher` and `description` keys.

The "Already updated" list was also per-run, so the rerun the documentation
recommends after a write failure reported "none" while a target was fully
installed. It is now seeded from the preflight, and the uninstall path — which
has the same two-target failure mode — reports it too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@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.

Round 2 of 3 — APPROVED

Claim: install and uninstall a Trae PreToolUse hook for RunCommand (project and global, plus ~/.trae-cn when it exists) and rewrite commands through rtk, leaving approval to Trae.

Scope: accept (frozen at round 1, not reopened).

CI: green on your ceae215f across all ten jobs, and green again on the fixup head 64a7010b.

Thank you for this one — every item came back with a regression test that actually pins it, and you reverted the interim RTK_AUDIT_DIR writer change rather than letting #1681 leak into scope. That was the right call.

Blocks merge — empty (was 2)

Both verified by mechanism, against a round-1 binary and a round-2 binary built in separate target directories and hash-checked:

ca248624 ceae215f
uninstall, matcher RunCommand|WriteFile "nothing to remove", hook left live removed
reinstall after editing timeout to 60 two rtk hook trae groups one
test (windows-latest) failed green on the real runner

All three optional items are fixed as well: BOM tolerance on all three sites (install, uninstall, hook stdin), both rtk.exe spellings recognised, and the partial-write diagnostic naming what had already been applied. I re-ran the whole round-1 matrix on the new head — idempotence, byte-identical user-content round-trip, CRLF, preflight-before-write, dry-run, malformed payloads, hook check parity with Codex — all still correct.

I also checked your new tests by mutation rather than by reading them: reintroducing the matcher gate fails test_trae_customized_hooks_… and test_trae_windows_command_…, and un-stripping the BOM fails trae_hook_rewrites_bom_prefixed_payloads. They are load-bearing. And I verified all four claims the new docs paragraph makes, including "rerun the install; completed targets will not receive duplicate hooks".

Fixup pushed — 64a7010b

Four things, and the first is mine rather than yours.

  1. Presence was matcher-blind. Asking you to match on the command alone fixed the round-1 defect and went one step too far: rtk hook trae registered only under a matcher that excludes RunCommand counted as installed, so rtk init --agent trae reported "already present" and skipped adding a registration that would actually fire. Presence now counts a group with no matcher, or one naming RunCommand among its |-separated tools; anything unrecognised counts as not covering it, so we add an entry that fires rather than skip one that never would. Removal stays matcher-blind, which is what round 1 asked for.
  2. Removal was type-blind. A user-authored {"type": "prompt", "command": "rtk hook trae"} entry was deleted and its group pruned, taking the user's matcher and description with it. A .bak made it recoverable, but a prompt entry is by construction not something RTK installs. Removal now skips an entry whose explicit type is not command, while still matching one with no type at all — which is the common hand-written shape your own tests use.
  3. Already updated was per-run. A target that preflighted as AlreadyPresent never entered pending, so it could never be listed. On the rerun your docs recommend, the message said Already updated: none while ~/.trae/hooks.json was fully installed — which is exactly the moment a user might hand-add a second entry. It is now seeded from the preflight.
  4. Uninstall had the same two-target failure mode with no diagnostic, so it now reports it too. Your docs paragraph only promised this for install, so this was an asymmetry rather than a broken claim.

This is why the ("ReadFile", 5, "prompt") row is gone from test_trae_customized_hooks_… — it asserted 1 and 2 as intended behaviour, so the code could not change without it. Five tests replace it, and each of the four fixes is mutation-verified: reverting any one fails exactly one named test. Gates on the fixup are fmt clean, clippy clean, 3837 passed / 0 failed, and I re-ran every previously-fixed behaviour to confirm nothing regressed.

If you disagree with any of it — particularly 1, where the right answer depends on how Trae actually reads matcher, which you can check and I cannot — say so and I will take it back out. You know that surface better than I do.

Your questions

  • "Please re-review the current head and approve/run the fork CI if approval is required." → Done. Approval is required on every push from a fork, so I approved the run on ceae215f and again on 64a7010b. Worth knowing for future PRs: until a maintainer approves it, a fork branch is never built, and a green CLA check alone does not mean CI passed.

Next

Nothing — approving. Closes #3234 and Closes #1676 are in place, so both close on merge.

Rounds: 2/3. Threads: 0 open, 1 resolved.

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.

Add hook support for Trae IDE (PreToolUse on RunCommand tool) [Agent] Add Trae.ai IDE support

3 participants