Skip to content

fix(runtime): handle RTK missing hook - #1539

Merged
zerob13 merged 1 commit into
devfrom
fix/windows-rtk
Apr 25, 2026
Merged

fix(runtime): handle RTK missing hook#1539
zerob13 merged 1 commit into
devfrom
fix/windows-rtk

Conversation

@zerob13

@zerob13 zerob13 commented Apr 25, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • Refactor

    • Improved classification and handling of RTK rewrite results for more robust command preparation and health check verification.
  • Tests

    • Added test coverage for edge case scenarios involving missing global hooks to ensure consistent rewrite behavior across different configurations.

@coderabbitai

coderabbitai Bot commented Apr 25, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Introduces a typed classification function classifyRtkRewriteResult to standardize RTK rewrite result handling across command preparation and health checks, replacing direct exit-code/stdout checks with computed classifications based on exit codes and output.

Changes

Cohort / File(s) Summary
RTK Rewrite Classification
src/main/lib/agentRuntime/rtkRuntimeService.ts
Added classifyRtkRewriteResult typed function to consolidate RTK outcome classification logic. Refactored command preparation and health-check verification to derive outcomes via the classifier instead of direct exit-code/stdout truthiness checks.
Test Parameterization & Edge Cases
test/main/lib/agentRuntime/rtkRuntimeService.test.ts
Parameterized createHealthCheckRunCommand to accept custom rewriteResult fixtures. Added test coverage for exit code 3 (missing global hook) scenario, verifying that command is marked rewritten and RTK health remains healthy.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A classifier hops in with care,
Exit codes parsed with flair,
Rewrite results now unified and clean,
One function to rule the RTK scene!
Tests confirm each edge case true,
Refactoring done, the path shines through! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(runtime): handle RTK missing hook' directly addresses the main change: adding classification logic to handle the RTK missing hook scenario (exit code 3) in the runtime service.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

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

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
test/main/lib/agentRuntime/rtkRuntimeService.test.ts (1)

178-292: Tests faithfully exercise the new classifier branch at both layers.

Both prepareShellCommand and startHealthCheck are covered for the code=3 + "No hook installed" path, and the health-check assertion that the rewrite invocation was actually issued is a nice belt-and-braces check.

Optional follow-up: consider one negative test where code=3 is paired with a non-matching stderr (e.g. a generic error) to lock down that this only opens the gate for the documented missing-hook signal and doesn't accidentally treat unrelated code=3 failures as rewrites.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/main/lib/agentRuntime/rtkRuntimeService.test.ts` around lines 178 - 292,
Add a negative test case to ensure we only treat exit code 3 as "missing global
hook" when stderr matches the documented message: create a test (mirroring the
existing positive ones) that uses prepareShellCommand and startHealthCheck with
a mocked runCommand returning code: 3 but stderr: 'Some other error' (or
similar), then assert that the command is NOT rewritten (result.rewritten false,
rtkApplied false, rtkMode 'bypass' or appropriate fallback) and that
startHealthCheck does not count this as a rewrite invocation; reference
prepareShellCommand and startHealthCheck to locate where the classifier behavior
is exercised.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/lib/agentRuntime/rtkRuntimeService.ts`:
- Around line 120-149: Replace the magic numbers and inline regex in
classifyRtkRewriteResult with named SCREAMING_SNAKE_CASE constants (e.g.
RTK_EXIT_SUCCESS, RTK_EXIT_BYPASS, RTK_EXIT_DENY, RTK_EXIT_NO_HOOK and
NO_HOOK_REGEX) and update the logic so that exit code 2 (RTK_EXIT_DENY) returns
the same bypass result as exit code 1; keep the existing special-case that
treats RTK_EXIT_NO_HOOK + "No hook installed" as a rewritten result but add a
short comment explaining why an otherwise-"ask" exit code is treated as
"rewritten". Ensure you update the checks in classifyRtkRewriteResult to use
these constants (and the NO_HOOK_REGEX) instead of hardcoded numbers/strings.

---

Nitpick comments:
In `@test/main/lib/agentRuntime/rtkRuntimeService.test.ts`:
- Around line 178-292: Add a negative test case to ensure we only treat exit
code 3 as "missing global hook" when stderr matches the documented message:
create a test (mirroring the existing positive ones) that uses
prepareShellCommand and startHealthCheck with a mocked runCommand returning
code: 3 but stderr: 'Some other error' (or similar), then assert that the
command is NOT rewritten (result.rewritten false, rtkApplied false, rtkMode
'bypass' or appropriate fallback) and that startHealthCheck does not count this
as a rewrite invocation; reference prepareShellCommand and startHealthCheck to
locate where the classifier behavior is exercised.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: d017d806-3fbd-4611-992c-6342c8605789

📥 Commits

Reviewing files that changed from the base of the PR and between cc22371 and 23ee53f.

📒 Files selected for processing (2)
  • src/main/lib/agentRuntime/rtkRuntimeService.ts
  • test/main/lib/agentRuntime/rtkRuntimeService.test.ts

Comment on lines +120 to +149
function classifyRtkRewriteResult(result: CommandResult): RtkRewriteResult {
const stdout = result.stdout.trim()
const stderr = result.stderr.trim()

if (result.code === 0 && stdout) {
return {
status: 'rewritten',
command: stdout
}
}

if (result.code === 3 && stdout && /No hook installed/i.test(stderr)) {
return {
status: 'rewritten',
command: stdout
}
}

if (result.code === 1) {
return {
status: 'bypass',
message: 'RTK rewrite did not match this command'
}
}

return {
status: 'failure',
message: stderr || stdout || 'rtk rewrite failed'
}
}

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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

rtk CLI rewrite command exit code conventions

💡 Result:

For rtk rewrite, the documented exit-code “conventions” (used by the Claude Code hook rewrite script / protocol) are: - Exit code 0: A rewrite was found and the command is auto-allowed. (Non deny/ask rules matched; hook should apply rewrite without requiring user approval.) - Exit code 1: No RTK equivalent exists for the command; pass through unchanged. - Exit code 2: A deny rule matched; pass through (do not apply rewrite). - Exit code 3: An “ask” rule matched; rewrite the command but let the host (e.g., Claude Code) prompt the user for approval. Important practical note: several Cursor/Claude Code hook issues show that if a hook treats any non-zero exit as failure (e.g., using ||), then rewrites with exit code 3 can be incorrectly discarded, and the hook may return {} instead of applying the rewritten command. Also, at least one issue indicates exit code 3 for read-only commands (ls/grep/find/wc/cat/tail) caused silent drops in permissive Claude Code sessions, leading to a recommendation/implementation to classify those as exit code 0 (auto-allow) instead—while keeping exit code 3 for state-modifying commands requiring an “ask”/approval flow.

Citations:


🏁 Script executed:

# First, read the file to understand context and see type definitions
head -n 130 src/main/lib/agentRuntime/rtkRuntimeService.ts | tail -n 70

Repository: ThinkInAIXYZ/deepchat

Length of output: 1845


🏁 Script executed:

# Check earlier in file for type definitions and any existing constants
head -n 70 src/main/lib/agentRuntime/rtkRuntimeService.ts

Repository: ThinkInAIXYZ/deepchat

Length of output: 1794


🏁 Script executed:

# Search for other references to RTK exit codes or CommandResult in the codebase
rg "result\.code" --type ts --type tsx -B2 -A2

Repository: ThinkInAIXYZ/deepchat

Length of output: 93


🏁 Script executed:

# Read the exact lines mentioned in the review (120-149)
sed -n '120,149p' src/main/lib/agentRuntime/rtkRuntimeService.ts

Repository: ThinkInAIXYZ/deepchat

Length of output: 701


🏁 Script executed:

# Search for any other references or usages of this function or exit codes
rg "classifyRtkRewriteResult\|exit.*code\|result\.code" --type ts -B1 -A1

Repository: ThinkInAIXYZ/deepchat

Length of output: 47


🏁 Script executed:

# Check if there are any comments or documentation about RTK exit codes
rg "exit.*code|RTK.*code|rewrite.*result" --type ts | head -20

Repository: ThinkInAIXYZ/deepchat

Length of output: 1610


🏁 Script executed:

# Check where this function is called and how the result is used
rg "classifyRtkRewriteResult" --type ts -B2 -A5

Repository: ThinkInAIXYZ/deepchat

Length of output: 1958


🏁 Script executed:

# Look for any comments or documentation about the "No hook installed" pattern
rg "No hook installed\|hook.*installed" --type ts -B3 -A3

Repository: ThinkInAIXYZ/deepchat

Length of output: 47


🏁 Script executed:

# Check if there's any handling for exit code 2 elsewhere
rg "code.*2\|exit.*2" src/main/lib/agentRuntime/rtkRuntimeService.ts

Repository: ThinkInAIXYZ/deepchat

Length of output: 47


Extract RTK exit-code constants and handle exit code 2 (deny rule).

The classifier hardcodes exit codes (0, 1, 3) and the "No hook installed" pattern inline. Per coding guidelines, these should be named constants using SCREAMING_SNAKE_CASE. More importantly, exit code 2 (deny rule matched) currently falls through to failure, but should map to bypass per RTK documentation—a deny rule means "do not apply rewrite," which aligns with bypass semantics.

Suggested fix
+const RTK_EXIT_CODE_REWRITTEN = 0
+const RTK_EXIT_CODE_BYPASS = 1
+const RTK_EXIT_CODE_DENY = 2
+const RTK_EXIT_CODE_MISSING_HOOK = 3
+const RTK_MISSING_HOOK_PATTERN = /No hook installed/i
+
 function classifyRtkRewriteResult(result: CommandResult): RtkRewriteResult {
   const stdout = result.stdout.trim()
   const stderr = result.stderr.trim()

-  if (result.code === 0 && stdout) {
+  if (result.code === RTK_EXIT_CODE_REWRITTEN && stdout) {
     return {
       status: 'rewritten',
       command: stdout
     }
   }

-  if (result.code === 3 && stdout && /No hook installed/i.test(stderr)) {
+  if (
+    result.code === RTK_EXIT_CODE_MISSING_HOOK &&
+    stdout &&
+    RTK_MISSING_HOOK_PATTERN.test(stderr)
+  ) {
     return {
       status: 'rewritten',
       command: stdout
     }
   }

-  if (result.code === 1) {
+  if (result.code === RTK_EXIT_CODE_BYPASS || result.code === RTK_EXIT_CODE_DENY) {
     return {
       status: 'bypass',
-      message: 'RTK rewrite did not match this command'
+      message: result.code === RTK_EXIT_CODE_DENY
+        ? 'RTK deny rule matched'
+        : 'RTK rewrite did not match this command'
     }
   }

The exit code 3 + "No hook installed" pattern also warrants a comment explaining why a normally-"ask" exit code is being treated as "rewritten" here.

📝 Committable suggestion

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

Suggested change
function classifyRtkRewriteResult(result: CommandResult): RtkRewriteResult {
const stdout = result.stdout.trim()
const stderr = result.stderr.trim()
if (result.code === 0 && stdout) {
return {
status: 'rewritten',
command: stdout
}
}
if (result.code === 3 && stdout && /No hook installed/i.test(stderr)) {
return {
status: 'rewritten',
command: stdout
}
}
if (result.code === 1) {
return {
status: 'bypass',
message: 'RTK rewrite did not match this command'
}
}
return {
status: 'failure',
message: stderr || stdout || 'rtk rewrite failed'
}
}
const RTK_EXIT_CODE_REWRITTEN = 0
const RTK_EXIT_CODE_BYPASS = 1
const RTK_EXIT_CODE_DENY = 2
const RTK_EXIT_CODE_MISSING_HOOK = 3
const RTK_MISSING_HOOK_PATTERN = /No hook installed/i
function classifyRtkRewriteResult(result: CommandResult): RtkRewriteResult {
const stdout = result.stdout.trim()
const stderr = result.stderr.trim()
if (result.code === RTK_EXIT_CODE_REWRITTEN && stdout) {
return {
status: 'rewritten',
command: stdout
}
}
if (
result.code === RTK_EXIT_CODE_MISSING_HOOK &&
stdout &&
RTK_MISSING_HOOK_PATTERN.test(stderr)
) {
return {
status: 'rewritten',
command: stdout
}
}
if (result.code === RTK_EXIT_CODE_BYPASS || result.code === RTK_EXIT_CODE_DENY) {
return {
status: 'bypass',
message: result.code === RTK_EXIT_CODE_DENY
? 'RTK deny rule matched'
: 'RTK rewrite did not match this command'
}
}
return {
status: 'failure',
message: stderr || stdout || 'rtk rewrite failed'
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/lib/agentRuntime/rtkRuntimeService.ts` around lines 120 - 149,
Replace the magic numbers and inline regex in classifyRtkRewriteResult with
named SCREAMING_SNAKE_CASE constants (e.g. RTK_EXIT_SUCCESS, RTK_EXIT_BYPASS,
RTK_EXIT_DENY, RTK_EXIT_NO_HOOK and NO_HOOK_REGEX) and update the logic so that
exit code 2 (RTK_EXIT_DENY) returns the same bypass result as exit code 1; keep
the existing special-case that treats RTK_EXIT_NO_HOOK + "No hook installed" as
a rewritten result but add a short comment explaining why an otherwise-"ask"
exit code is treated as "rewritten". Ensure you update the checks in
classifyRtkRewriteResult to use these constants (and the NO_HOOK_REGEX) instead
of hardcoded numbers/strings.

@zerob13
zerob13 merged commit 8bcd1f3 into dev Apr 25, 2026
3 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Apr 26, 2026
@zhangmo8
zhangmo8 deleted the fix/windows-rtk branch April 30, 2026 05:55
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