`jq` hook-rewrite fails with opaque "rtk: No such file or directory" when jq is not installed, masking the real cause
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 48/100
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
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 — likelyinstall.shper INSTALL.md) - Hook: Claude Code global
~/.claude/settings.json,PreToolUsematcherBash→rtk 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 ajq→rtk jqrule (per PR #1911), butrtk jqhas no dedicated implementation yet (per #1364) — it's an unregistered/fallback subcommand that shells out to the literaljqbinary. - 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)
- Never Block, properly: if the
rtk jqpassthrough can't findjqonPATH, fall back to running the original raw command (jq ...) instead ofrtk jq ..., so the user gets the standard shell "command not found" instead of an RTK-branded error. - 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. - Document/gate the dependency: warn (or fall back per #1) when
jqis missing right before thejqrewrite rule fires, or listjqas 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 jqcommand feature request (confirmsrtk jqis currently a bare transparent fallback, 0% savings) - #1911 — PR adding jq/yq rewrite rules (introduces the
jq→rtk jqrewrite this issue is about) - #3263 —
rtk statsexits 127 with the same opaque "No such file or directory (os error 2)" message - #2620 —
rtk grep --ultra-compactreturns 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from rtk-ai/rtk
-
area:cli bug filter-quality good first issue priority:medium
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
-
bug core output-formatting
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
-
uv_cmd discards print_with_hint's return value, so the tee hint is not counted and savings read 100% Openanalytics bug python
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
-
area:cli bug good first issue platform:windows priority:medium resolved-pending-close
Difficulty 1/5 Under an hour Newbie friendliness 92/100
-
area:cli bug good first issue priority:high
Difficulty 1/5 Under an hour Newbie friendliness 92/100
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
kwakseongjae/auto-hwp#319 ·
-
Difficulty 1/5 Under an hour Newbie friendliness 72/100
bevyengine/bevy#25861 ·
-
comp-datalake
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
ClickHouse/ClickHouse#121222 ·
-
enhancement remote
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
A-linter
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
oxc-project/oxc#26863 ·