Skip to main content

Module output_tail

Module output_tail 

Source
Expand description

A command’s own output: the tail of the file the CLI streams it to.

The CLI’s local-bash tasks write the command’s stdout to a file on disk rather than sending it over the wire (confirmed in ~/Projects/forge/.claude/skills/claude-cli-upgrade/reference-captures/monitor.jsonl), so the tail is a file read. The readers are the transport - which resolves the path from its held conversation and answers a view’s ReadCallOutput with the result - and the views that hold a path already (a view tails a Monitor’s file on its own tick).

Two ways a path is found, and the ending frame’s own word wins when both exist: task_notification.output_file names the file when the task ends, and until then the path is composed from the task id the wire carries (compose_task_output_path, under task_output_base - both crate-internal, so named rather than linked). Neither reads the launch sentence: forge resolves the file structurally, never from prose.

Re-exports§

use std::collections::VecDeque; 🔒
use std::ffi::OsString; 🔒
use std::fs::File; 🔒
use std::io::BufRead; 🔒
use std::io::BufReader; 🔒
use std::io::Seek; 🔒
use std::io::SeekFrom; 🔒
use std::path::Path; 🔒
use std::path::PathBuf; 🔒

Constants§

CALL_OUTPUT_MAX_LINES
How many lines of a call’s output a ReadCallOutput answers with.
TAIL_WINDOW_BYTES
Largest slice of the file read at a time. Output files grow without bound (cargo build, npm install) and this runs inline on a view’s own tick, so the read seeks to within this window of the end rather than reading the whole file each time. 64 KiB comfortably holds the last few lines at any realistic line length.

Statics§

LAYOUT_PROBED 🔒
The one check probe_task_output_layout guards.

Functions§

base_of 🔒
task_output_base’s own rule, split out so it can be tested without writing the process’s environment - edition 2024 makes that unsafe and this workspace forbids unsafe code.
compose_task_output_path 🔒
The path a running task’s own output file sits at, composed from the fields the wire carries rather than parsed out of prose: <base>/claude-<uid>/<cwd-slug>/<session-id>/tasks/<task-id>.output.
probe_task_output_layout 🔒
One check per process that <base>/claude-<uid> is where this build expects it, so a wholesale CLI layout change announces itself once rather than starving every tail in silence.
read_output_file_tail
Read the last max_lines lines of path into a Vec ordered oldest-first. Returns None on any read error (file missing, permission denied, a read that failed) so the caller can distinguish “could not read, keep the tail already held” from “the file is genuinely empty”. The empty-file case returns Some(vec![]).
resolve_call_output_path
The file a call’s output went to, from the frames a conversation carries.
resolve_call_output_path_at 🔒
resolve_call_output_path with the base named, so the composition can be exercised against a scratch tree without writing the process env.
task_output_base 🔒
The base directory the CLI keeps its per-session task files under: realpath($CLAUDE_CODE_TMPDIR ?? /tmp), read from the same environment the spawned child runs with (forge sets none of its own). Rule 14: never forge’s own cwd.