Tail the log: a terminal reading pane for Claude Code sessions
The text Claude writes for you is a small part of what the terminal shows. claude-reader tails the transcript file Claude Code already keeps and shows only that part, rendered, in a second pane. No hooks, no server, and a few design choices dictated by the pane next door.
Vikas Goenka · 17 August 2026 · github.com/vikasgrac/claude-reader · PyPI
Most of my work happens in Claude Code, often in sessions that run for hours. After a while, finding an earlier response becomes difficult. Claude might have just shared an important finding, asked me a question, or proposed three options. I remember that it said something related forty minutes ago, so I start scrolling. I pass command output, diffs, tool calls, and their results. Eventually I find the paragraph, several screens back. Its headings are literal hash characters, and its list items are plain dashes.
That paragraph was meant for me. Most of the surrounding transcript was produced for the machine or by the machine. The terminal displays all of it as the same stream of lines. Claude Code needs to show its work, so the amount of output is not the issue. What I was missing was another view that kept only the human-facing conversation, rendered it properly, and made older messages easy to revisit.
Web viewers for Claude Code transcripts already exist, including ones that render markdown. They also put a browser and server into a workflow that, for me, already lives in the terminal. I did not want another application and window. I wanted another terminal pane. That became claude-reader, now available through PyPI and GitHub. It is an independent project and is not affiliated with Anthropic. Its foundation is one simple approach, followed by a few practical choices that only became clear during use.
The central idea: follow the transcript
While a session is running, Claude Code saves it under ~/.claude/projects/<project>/<session-id>.jsonl. The file contains one JSON object per line and grows through appends. Assistant turns contain typed blocks such as text, tool_use, and thinking. The transcript already distinguishes the content I need. claude-reader retains the text blocks and my prompts, then removes everything else.
There are no hooks into Claude Code or the terminal, and there is no server. The reader opens the JSONL file and stores its current byte position. Twice each second, it checks and processes the newly appended data. It also watches the inode and file size. If the transcript gets replaced or truncated, the reader reloads from the beginning. An unfamiliar record, a new record type, a half-written final line, or a null value where text was expected is ignored instead of crashing the application.
Reading the file directly has three useful consequences. Any terminal can run it. The same method works through ssh. Changes to the Claude Code interface do not affect it because the interface is never involved. Textual, the Python TUI framework, is its only dependency.
What it looks like
┌────────────────────────────────┬───────────────────────────────────────────────┐ │ claude-reader — Improve message readability [5e86140a] │ ├────────────────────────────────┼───────────────────────────────────────────────┤ │ Can we make this a TUI? │ Claude · 07:25:25 │ │ you · 07:25:01 │ ───────────────────────────────────────── │ │ You're right that a web page… │ │ │ claude · 07:25:25 │ You're right that a web page adds friction, │ │ So I run it in a second pane? │ and the terminal-native version is just as │ │ you · 07:27:37 │ feasible. But one framing correction first… │ │ Almost. One nuance to get… │ │ │ claude · 07:27:50 │ - tmux split │ │ Let's build it. │ - your terminal's native split │ │ you · 07:30:15 │ - or a second window │ │ ▶ Setting up in the project… │ │ │ claude · 07:30:38 │ So the deliverable is one command… │ └────────────────────────────────┴───────────────────────────────────────────────┘
The left sidebar lists messages from Claude and me, with the newest at the bottom. The right side displays the selected message as rendered markdown, preserving headings, lists, and code blocks. This view comes from the session where I designed the tool. The first entry is my question about building a TUI rather than a web page.
Two-minute tour
- Launch Claude Code normally inside a project:
cd ~/work/myapp && claude. - Create another terminal pane using tmux
prefix %, or open another tab in Ghostty, Kitty, iTerm2, Warp, or Windows Terminal with WSL. Runcd ~/work/myapp && claude-readerthere. The reader locates and follows the project's newest session. When multiple sessions have been active during the past hour, it opens a picker. - Give Claude a longer task, such as writing a plan or reviewing code. Tool activity continues scrolling in the Claude pane. The reader remains still until Claude produces prose, then shows that message with formatting.
- Use
kto move through previous messages. Selecting an older item stops auto-follow, but incoming messages still appear in the sidebar. Usefto return to the latest message and start following again. - Press
uto remove your prompts from the view. Presssfor a full-width reading pane without the sidebar. Presscto copy the selected message for use in notes or a commit message. - If the project has several recent sessions, press
pto choose between them. Sessions that are still live are identified in the picker. Pressqto exit. Claude Code remains unaware because claude-reader has only read its transcript file.
Three details that made a difference
claude-reader normally sits beside Claude Code. That neighbouring pane shaped several design choices more than the reader's feature list did.
Mouse support is disabled unless requested. Textual and similar TUI frameworks generally turn on any-motion mouse reporting for features such as hover states and drag scrolling. In a split layout, that caused a problem outside the reader. Text selection by dragging stopped working in the adjacent Claude pane. Mouse tracking is therefore disabled unless the reader starts with --mouse. Scrolling still works under tmux because tmux translates wheel input into arrow keys for full-screen applications that are not tracking the mouse.
The header has no clock. Showing the session name beside a clock seemed natural. The clock would force a repaint every second, though. Terminal output that arrives during a drag-selection cancels that selection. claude-reader therefore produces no new output until another message arrives. Its lack of movement is intentional.
Clipboard handling uses tmux. tmux ignores OSC 52 clipboard escapes sent directly by an application. When c is used inside tmux, claude-reader instead passes the message to tmux load-buffer -w. It creates a named temporary buffer, forwards the text to the outer terminal, and immediately deletes the buffer so the content does not remain in tmux's paste history. This also works through ssh when the terminal supports OSC 52. Clipboard copying is the only operation that sends text out of the application. Transcript reading is local, with no network requests.
These choices are not complicated. I found each one by running the reader beside an actual Claude session for a day and watching how it affected the other pane.
A review before publication
I built the initial version in one sitting on 15 August. It was about 470 lines and I started using it that afternoon. Two days later, before the PyPI release, I asked a different model from the one that built it to review the code. That review found concrete failures:
- A valid JSON record with an unexpected value such as
message: nullortext: nullstopped the polling loop. One unusual transcript line could leave the reader frozen for the remaining session. - Because the tailer used UTF-8 text mode, it could raise when Claude was still writing and the current end of the file split a multibyte character.
- Memory usage had no limit. Every transcript text block created a widget that stayed in memory indefinitely.
- There was no real option parser, so mistyped options were accepted without warning.
Version 0.2.0 replaces those weak points with a type-checked parser and a binary tailer that decodes individual lines. The tailer monitors file size and inode so it can reload a rewritten transcript. --max-messages limits retained messages, with 500 as the default, which keeps long transcripts quick to open and inexpensive to hold. argparse now reports usage mistakes with exit code 2, while a transcript that cannot be opened returns exit code 1. File access has OSError handling throughout. The suite contains 33 tests, including a Textual pilot test that operates the UI. The reader's purpose stayed the same, but these changes made it something I was comfortable releasing to other people.
What it does not try to be
Other tools cover nearby needs. Web dashboards can display Claude Code transcripts. tmux managers including claude-squad, ccmanager, and tmux-claude-session-manager address workflows with many Claude sessions. claude-reader has a smaller scope. It is a terminal-native, file-only reading pane. It does not yet offer search, a compact record of tool activity between messages, or a single tmux command that starts both Claude and the reader. Issues and pull requests are welcome.
Install
pipx install claude-reader # or uv tool install claude-reader # or, without installing uvx claude-reader
It requires Python 3.10+ and runs on Linux, macOS, or WSL. The MIT-licensed source and README are at github.com/vikasgrac/claude-reader. The PyPI package is at pypi.org/project/claude-reader/.
If long Claude Code sessions are part of your daily work, run it in a split for an afternoon. When Claude next writes something you need to read again, see whether you can find it without scrolling.
More from the Labs
I benchmark open models against frozen frontier anchors on my Open Model Benchmark, and write up what the numbers actually mean. RSS is the reliable way to catch new posts.