rtk-ai / rtk-ai/rtk

`rtk read --max-lines N` outputs only N/2 lines — `head -N` hook rewrite silently under-delivers half the requested lines

Open
#3,370 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

area:cli bug priority:high
Dominant language
Rust
Stars
81.1k
Forks
5.1k
Avg merge
4d 21h
Merged PRs (30d)
35

Description

Environment

  • rtk 0.38.0 and 0.44.2 (both reproduce)
  • macOS (Darwin 25.5.0, arm64), Homebrew install
  • Claude Code, hook: PreToolUsematcher: "Bash"rtk hook claude

Summary

--max-lines N emits exactly N/2 content lines (plus a [K more lines] marker), not N. Because the Claude Code hook rewrites head -N <file> to rtk read <file> --max-lines N, every agent head -N receives half the lines it asked for.

Repro

$ python3 -c "open('long.txt','w').write(''.join(f'line {i}: some content here\n' for i in range(3000)))"
$ for n in 5 10 20 50 100 200; do
    echo "max-lines $n -> $(rtk read long.txt --max-lines $n | grep -c '^line ')"
  done
max-lines 5 -> 2
max-lines 10 -> 5
max-lines 20 -> 10
max-lines 50 -> 25
max-lines 100 -> 50
max-lines 200 -> 100

Expected (matching head -50): 50 lines. Actual: 25 lines + [2975 more lines].

Hook path that makes this user-visible:

$ rtk hook check 'head -50 big.log'
rtk read big.log --max-lines 50        # → agent gets 25 lines

--tail-lines N is exact (verified against tail -N, byte-identical); only --max-lines halves.

Impact

  • Any agent running head -N file silently gets N/2 lines. The truncation marker is visible in standalone use, but the count contradicts what was requested.
  • On 0.38.0 this also poisoned pipelines (head -200 f | grep X → grep searched only 100 lines and the marker was consumed by grep → silent false negative). 0.44.2 no longer rewrites head inside pipelines, which contains that failure mode — but the standalone halving remains.

Additional observation (savings accounting)

rtk read ... --max-lines/--tail-lines invocations are logged to history.db with original_cmd synthesized as plain cat <file> (flags erased), input_tokens = the full file, and the difference booked as savings. Example measured entry: a --max-lines 20 read of a 1.27 MB file logged as cat <file>, input=318,445 tokens, output=119, savings 99.96%. A raw head -20 would have produced the same small output without rtk, so the full-file baseline overstates real savings. In our 3-month ledger, 205 such entries account for 71.5M of 79.4M total rtk read "saved" tokens (~90%). Related in spirit to #3339 (different mechanism). Happy to split this into its own issue if you prefer.

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.

Research direction

Reproduce the issue with the provided Python file-generation command and the rtk read --max-lines loop. Trace the implementation of rtk read and the Claude hook rewrite for head -N; compare --max-lines with the verified --tail-lines behavior. Done means --max-lines N emits N content lines, the hook preserves head semantics, and relevant command behavior is covered by tests.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
cli
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
66/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.