Reading paths from stdin

gnaw can take its file list from standard input instead of walking a directory: pipe one repo-relative path per line and gnaw sources exactly those files. This page is the precise contract; the how-to covers the day-to-day workflows.

git diff --name-only | gnaw

When stdin mode activates

gnaw reads stdin as a path list only when all of these hold:

ConditionWhy
stdin is not a terminal (it's piped or redirected)A human at a prompt isn't sending a list
no path argument was givenAn explicit path means "walk this"; it always wins
not in TUI mode (--tui)The TUI drives its own selection
not the internal clipboard daemonThe daemon uses stdin for its own payload

If any condition fails, gnaw behaves as before. In particular, a bare gnaw at an interactive terminal prints help, and gnaw . (or any explicit path) walks the tree even inside a pipeline.

How paths are resolved

Each non-blank line is trimmed, joined onto the root, and canonicalized:

  • Relative paths resolve against the root (the path argument, default .).
  • Paths that resolve outside the root are dropped. After canonicalization, a path that doesn't sit under the root is discarded โ€” a piped ../../etc/passwd can't escape the allowed root.
  • Paths that don't exist are dropped. Deleted files (which still appear in git diff --name-only) fail to canonicalize and are skipped silently.
  • Binary and empty files are dropped during extraction, identical to a normal walk.

Blank lines are ignored, so trailing newlines and empty input are harmless; empty input yields an empty selection rather than an error.

Ordering

The surviving files are sorted by path, not kept in the order they arrived on stdin. This keeps output byte-stable for snapshot tests and matches the other sources. If you need a specific order, it has to come from the rendered content, not the pipe order.

Interaction with other features

Filtering is bypassed. The piped list is treated as authoritative โ€” you named exactly these files โ€” so --include / --exclude and .gitignore rules do not apply to it. Binary/empty dropping and secret scanning still run.

Secret scanning still applies. The scrubber stage runs as normal, so --secret-scan=block will still halt a stdin run that hits a finding, and redact still masks.

Stdin wins over the git source axes. If a path list is present, it takes precedence over --git-diff-shas and the git-narrative source selection. A run is either "these piped files" or "this git range," not both.

--full-directory-tree is ignored. The source tree is always derived from the piped files, so it lists exactly those paths and never expands to the whole repository.

Templates resolve normally. Because a stdin run has no --git-diff-shas, the default template (or your --template) is used. The git-narrative templates' {{git_diff}} section stays empty unless you also pass --diff, which loads the working-tree diff as chrome alongside the piped contents.

See also