`jq` hook-rewrite fails with opaque "rtk: No such file or directory" when jq is not installed, masking the real cause

Open
#3,956 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
48/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Active
Tech stack
rust
Domain
cli

Research direction

Start at the rtk hook claude entry point and the discover/rewrite registry, then trace the fallback path used by rtk jq when spawning jq fails. Review CONTRIBUTING.md's “Never Block” principle and the shared spawn-error formatting implicated by #3263 and #2620. Done means a missing jq failure identifies the actual binary or falls back to the original command without an opaque rtk error.

Written by the indexing model from the issue text.

Description

area:cli bug help wanted priority:medium

Summary

RTK's Claude Code hook (rtk hook claude) silently rewrites any jq ... Bash command into rtk jq .... rtk jq is a transparent passthrough to the system jq binary (per #1364, it currently does no filtering — "0% savings"). If jq is not installed, this passthrough fails, but instead of a normal "command not found" error, it prints a generic, misleading message that names rtk instead of the actual missing binary (jq):

[rtk: No such file or directory (os error 2)]

This looks like rtk itself is broken or missing, not that an optional dependency (jq) is absent. It cost real debugging time — hit twice on two different laptops — because the error gives no indication that jq is the actual problem, or that RTK depends on it at all. jq isn't documented anywhere as a dependency.

This also isn't jq-specific: the exact same opaque string shows up in unrelated contexts, e.g. #3263 (rtk stats) and #2620 (rtk grep --ultra-compact). It looks like a systemic pattern in however RTK surfaces subprocess-spawn failures, which violates the "Never Block" design principle in CONTRIBUTING.md ("If a filter fails, fall back to raw output... every hook must handle malformed input gracefully").

Environment

  • rtk --version: 0.48.0
  • OS: Linux (WSL2, Ubuntu-based), platform linux
  • Installed via: (binary present at ~/.local/bin/rtk; install method not recorded in shell history on this machine — likely install.sh per INSTALL.md)
  • Hook: Claude Code global ~/.claude/settings.json, PreToolUse matcher Bashrtk hook claude

Before: jq not installed

$ which jq
(exit 127 — not found)

$ jq empty /home/jgraver/.claude/settings.json
[rtk: No such file or directory (os error 2)]
$ echo $?
127

Reproduced directly against the hook, bypassing Claude Code entirely:

$ echo '{"tool_name":"Bash","tool_input":{"command":"jq empty /home/jgraver/.claude/settings.json"}}' | rtk hook claude
{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecisionReason":"RTK auto-rewrite","updatedInput":{"command":"rtk jq empty /home/jgraver/.claude/settings.json"}}}

$ rtk jq empty /home/jgraver/.claude/settings.json
[rtk: No such file or directory (os error 2)]
$ echo $?
127

$ rtk jq --help
[rtk: No such file or directory (os error 2)]

Note rtk jq --help fails identically — jq isn't a real registered subcommand in rtk's CLI (it doesn't appear in rtk --help's command list; only json does), so this looks like an unknown-subcommand fallback path that tries to exec a literal jq binary and reports the spawn failure with a hardcoded rtk: label instead of the actual program name it tried to run.

Without the hook, the same original command (jq empty ...) run raw in bash gives a standard, comprehensible error:

$ jq empty /home/jgraver/.claude/settings.json
bash: jq: command not found

So the hook rewrite actively makes a missing-dependency failure harder to diagnose than doing nothing at all.

After: jq installed (jq-1.7, via apt-get install jq)

$ which jq
/usr/bin/jq

$ jq empty /home/jgraver/.claude/settings.json && echo OK
OK

$ echo '{"tool_name":"Bash","tool_input":{"command":"jq empty /home/jgraver/.claude/settings.json"}}' | rtk hook claude
{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecisionReason":"RTK auto-rewrite","updatedInput":{"command":"rtk jq empty /home/jgraver/.claude/settings.json"}}}

$ rtk jq empty /home/jgraver/.claude/settings.json && echo OK
OK

Everything works once the underlying binary exists. This confirms the rewrite/passthrough logic itself is fine — the only problem is the failure mode when the dependency is absent.

Root cause (best guess from black-box testing; I don't have a Rust toolchain available to confirm against source)

  • The discover/rewrite registry added a jqrtk jq rule (per PR #1911), but rtk jq has no dedicated implementation yet (per #1364) — it's an unregistered/fallback subcommand that shells out to the literal jq binary.
  • When that exec fails (ENOENT), the error is formatted generically as [rtk: {error}] rather than naming the binary that was actually being spawned, and rather than falling back to raw execution of the original (pre-rewrite) command as CONTRIBUTING.md's "Never Block" principle calls for.

Suggested fixes (any one of these would resolve it)

  1. Never Block, properly: if the rtk jq passthrough can't find jq on PATH, fall back to running the original raw command (jq ...) instead of rtk jq ..., so the user gets the standard shell "command not found" instead of an RTK-branded error.
  2. Fix the error message: when a passthrough subprocess spawn fails, name the actual binary that failed to spawn, not a hardcoded rtk: label. This would also fix the identical symptom in #3263 and #2620 — seems worth fixing at the shared spawn-error-formatting site rather than per-subcommand.
  3. Document/gate the dependency: warn (or fall back per #1) when jq is missing right before the jq rewrite rule fires, or list jq as an optional dependency in INSTALL.md / README.md so users know to install it before the rewrite silently activates.

Related existing issues/PRs (searched before filing — apologies if I've missed context)

  • #1364 — rtk jq command feature request (confirms rtk jq is currently a bare transparent fallback, 0% savings)
  • #1911 — PR adding jq/yq rewrite rules (introduces the jqrtk jq rewrite this issue is about)
  • #3263 — rtk stats exits 127 with the same opaque "No such file or directory (os error 2)" message
  • #2620 — rtk grep --ultra-compact returns the same opaque error and silently drops matches with exit 0
  • #3361 — tracking issue for broader proxy support (python3/jq/date/stat/etc.)

Note on #864 (open, "warn when jq is missing during hook installation"): superficially similar but not a fix for this — worth a maintainer look regardless. #864 addresses the old shell-script hook (hooks/rtk-rewrite.sh), which itself needed jq to parse the JSON payload from Claude Code; missing jq there caused the script to exit silently and every rewrite to no-op with zero indication of failure. #864 was explicitly framed as "a stopgap until #785 lands, which eliminates the jq dependency entirely by replacing the shell hook with a native rtk hook claude subcommand." #785 itself was closed without merging, but the native rtk hook claude subcommand it describes clearly landed some other way — I'm on rtk 0.48.0 and the native hook parses JSON fine with no jq on PATH at all. So #864's original motivation looks obsolete (no shell script left to fail silently), and it's been sitting dirty/unmergeable since March. This issue's jq dependency is different and newer: it's not the hook's own input parsing, it's the jq-command rewrite rule (#1911) routing a user's jq invocation through an unimplemented rtk jq passthrough. Might be worth closing #864 as superseded, or repurposing it for this instead — maintainers' call.

Happy to help verify a fix once one lands, or provide more repro detail.


wickedTangent / 🤖 Generated with Claude Code

Dominant language
Rust
Stars
81.1k
Forks
5.1k
Avg merge
4d 11h
Merged PRs (30d)
40

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from rtk-ai/rtk

All issues in rtk-ai/rtk

Similar issues

More Rust issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.