junegunn/fzf
:cherry_blossom: A command-line fuzzy finder
Single-maintainer risk — review before adopting
- ✓Last commit 3d ago
- ✓5 active contributors
- ✓MIT licensed
- ✓CI configured
- ✓Tests present
- ⚠Small team — 5 top contributors
- ⚠Single-maintainer risk — top contributor 89% of commits
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Embed this verdict
[](https://repopilot.app/r/junegunn/fzf)Paste into your README — the badge live-updates from the latest cached analysis.
Onboarding doc
Onboarding: junegunn/fzf
Generated by RepoPilot · 2026-05-05 · Source
Verdict
WAIT — Single-maintainer risk — review before adopting
- Last commit 3d ago
- 5 active contributors
- MIT licensed
- CI configured
- Tests present
- ⚠ Small team — 5 top contributors
- ⚠ Single-maintainer risk — top contributor 89% of commits
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
TL;DR
fzf is a general-purpose command-line fuzzy finder written in Go that reads a list of items from stdin (or files/processes) and provides an interactive TUI for fuzzy-searching and selecting them. It uses a custom fuzzy-matching scoring algorithm and renders its interface via the tcell/v2 terminal library. It ships as a single static binary with shell integrations for Bash, Zsh, and Fish (in shell/), plus Vim/Neovim plugins (plugin/fzf.vim). The core fuzzy-finder logic lives under src/ (e.g., src/actiontype_string.go hints at a large action/event system for key bindings). main.go is the binary entry point. Shell integrations are self-contained scripts in shell/ (completion.bash, key-bindings.zsh, etc.), and the Vim plugin is a single file at plugin/fzf.vim.
Who it's for
Power users and developers who live in the terminal and want a universal interactive filter — piping file lists, git commits, process IDs, shell history, or any text through fzf to quickly select items. Contributors are typically Go developers familiar with terminal/TUI programming and systems-level concerns like Unicode rendering and ANSI escape codes.
Maturity & risk
Extremely mature and production-ready: the repo has hundreds of thousands of stars, has been actively maintained since ~2013, has CI pipelines for Linux, macOS, and Windows (in .github/workflows/), and a CHANGELOG.md tracking years of releases. Commit and release activity is recent and continuous, with a goreleaser config (.goreleaser.yml) for automated binary distribution.
Very low risk: the dependency footprint is small and well-chosen — tcell/v2 for terminal rendering, fastwalk for fast directory traversal, uniseg for Unicode segmentation, and go-shellwords for argument parsing. It is primarily maintained by junegunn (single-maintainer risk exists), but the project's extreme stability, broad adoption, and years of production use significantly mitigate that. No heavy transitive dependency chain.
Active areas of work
The .github/workflows/ directory shows active CI across linux.yml, macos.yml, and a winget.yml for Windows package manager submission, indicating ongoing multi-platform release work. The dependabot.yml suggests automated dependency updates are running. The CHANGELOG.md would show the most recent feature additions, which from the repo structure likely include popup mode (--popup flag mentioned in README) and continued event-action binding enhancements.
Get running
git clone https://github.com/junegunn/fzf.git && cd fzf && go build -o fzf main.go && ./fzf # or use the Makefile: make build # To run tests: go test ./src/... # To install with shell integration: ./install
Daily commands: make build # produces ./fzf binary make test # runs Go test suite make install # installs to $GOPATH/bin
Or directly:
go run main.go
Map of the codebase
src/core.go— Orchestrates the entire fzf pipeline: wires together the reader, matcher, merger, and terminal into the main event loop every contributor must understand.src/options.go— Defines and parses every CLI flag and option; adding any new feature almost always starts here to expose it to users.src/terminal.go— The largest and most complex file — drives all interactive UI rendering, key bindings, action dispatch, and user-facing behavior.src/pattern.go— Implements query parsing and fuzzy/exact/regex pattern logic that sits at the heart of fzf's matching semantics.src/algo/algo.go— Contains the core fuzzy-matching and scoring algorithms (Smith-Waterman inspired) that define fzf's primary value proposition.src/matcher.go— Coordinates concurrent matching workers, result merging, and chunk iteration — the performance-critical search dispatch layer.src/tui/light.go— Implements the lightweight built-in terminal renderer used when tcell is not required, making it the default rendering path for most users.
How to make changes
Add a new CLI option / flag
- Add a new field to the Options struct and register its flag string in parseOptions() (
src/options.go) - Define any new ActionType constant if the option triggers a user action (
src/constants.go) - Regenerate the actiontype_string.go stringer by running
go generateor adding the string manually (src/actiontype_string.go) - Consume the new option field in the terminal initialisation or event loop (
src/terminal.go)
Add a new key binding / action
- Add the new action constant to the ActionType iota block (
src/constants.go) - Map the action name string to the constant in the actionTypeMap or parseActions helper (
src/options.go) - Implement the action handler case inside the doAction() switch in the terminal event loop (
src/terminal.go) - Add default shell key binding if the action should be globally accessible (
shell/key-bindings.bash)
Add a new matching / scoring algorithm
- Implement the scoring function following the existing AlgoFn signature (runes, caseSensitive, forward, pattern, pos) in algo.go (
src/algo/algo.go) - Register the new algorithm in the term-type → AlgoFn dispatch table inside buildPattern() (
src/pattern.go) - Expose a CLI flag or query-syntax prefix to select the new algorithm (
src/options.go) - Add unit tests covering scoring edge-cases and benchmark comparisons (
src/algo/algo_test.go)
Add a new TUI rendering backend
- Implement the Renderer interface (Init, Pause, Resume, Clear, RefreshWindows, NewWindow, Close, etc.) in a new file under src/tui/ (
src/tui/light.go) - Add build-tag or runtime selection logic to choose the new renderer (
src/tui/dummy.go) - Wire the renderer selection into terminal initialisation based on options (
src/terminal.go)
Why these technologies
- Go — Single statically-linked binary with no runtime dependencies; goroutines make the parallel matching pipeline straightforward; fast compilation suits a developer tool.
- Custom ANSI TUI renderer (tui/light.go) — Avoids a heavy TUI library dependency for the common case; keeps startup time minimal and the binary small; tcell is available as an opt-in alternative.
- Smith-Waterman-inspired scoring (src/algo/algo.go) — Provides position-aware scoring that rewards contiguous matches and beginning-of-word hits, which produces more intuitive rankings than simple edit distance.
- SIMD via assembly (indexbyte2_amd64.s / _arm64.s) — Inner-loop byte scanning is the bottleneck on large inputs; architecture-specific SIMD gives 3–5× speedup with pure-Go fallback for portability.
- HTTP listen server (src/server.go) — Enables scriptable remote control (--listen) so external processes and shell scripts can inject actions without reimplementing the TUI protocol.
Trade-offs already made
-
Chunk-based item storage (ChunkList)
- Why: Allows the reader and matcher to operate concurrently without locking the entire list; new chunks are appended atomically.
- Consequence: Memory is allocated in fixed 512-item blocks even for small inputs; random access requires chunk index arithmetic.
-
Pattern result cache keyed by query string
- Why: Backspacing a query character reuses the cached result for the shorter prefix instantly instead of re-scoring all items.
- Consequence: Cache entries are unbounded in count per session and hold full result slices, so memory grows with the number of distinct queries typed.
-
Built-in ANSI renderer as the default rather than tcell
- Why: Dramatically reduces startup time and binary size; tcell's abstraction layer adds overhead for a tool where latency matters.
- Consequence: The light renderer must manually handle terminal quirks (mouse, bracketed paste, 256-color vs truecolor) that tcell would normalise automatically.
-
Single options.go file for all
- Why: undefined
- Consequence: undefined
Traps & gotchas
The src/ package uses Go build tags for platform-specific terminal handling (especially Windows via PowerShell/install.ps1 and separate code paths). tcell/v2 requires a real TTY — running tests or the binary in a non-TTY environment (e.g., piped CI) will behave differently. The actiontype_string.go file is auto-generated with 'go generate' and must not be manually edited. The Ruby files (Gemfile, .rubocop.yml) are for the test suite (likely integration tests written in Ruby) — you need Ruby installed to run the full test suite, not just Go.
Architecture
Concepts to learn
- Smith-Waterman-style fuzzy scoring — fzf's core value is its scoring algorithm that ranks fuzzy matches by quality (consecutive chars, start of word, etc.) — understanding this explains why results appear in the order they do.
- tcell terminal cell model — fzf renders its TUI using tcell/v2's cell-based screen model rather than raw ANSI codes — understanding how tcell abstracts terminal capabilities is essential for modifying the display layer.
- Unicode grapheme cluster segmentation — fzf uses github.com/rivo/uniseg to correctly measure display width of Unicode text (emoji, CJK, combining chars) — this is why it handles multi-byte characters correctly in the TUI.
- ANSI/VT escape codes — fzf's color and cursor positioning output relies on VT100/ANSI escape sequences — you need to understand these to debug rendering issues or add new display features.
- Shell coprocess / process substitution — fzf integrates with shells via process substitution and subshell patterns (visible in shell/key-bindings.bash) — understanding how the shell forks and connects fzf's stdin/stdout is critical for debugging integration bugs.
- Event-action binding system — fzf's --bind flag maps terminal events (keypresses, mouse, load) to a rich action language — the entire extensibility model is built on this, and src/actiontype_string.go enumerates every possible action.
Related repos
sharkdp/fd— Fast alternative to 'find' frequently piped into fzf for file selection — a canonical companion tool.BurntSushi/ripgrep— rg output is one of the most common inputs to fzf for interactive grep workflows (fzf + rg is a standard pairing).junegunn/fzf.vim— The official higher-level Vim plugin built on top of plugin/fzf.vim, providing :Files, :Buffers, :Rg commands.lotabout/skim— A Rust-based fuzzy finder that is a direct fzf alternative with nearly identical interface, useful for understanding design tradeoffs.peco/peco— An earlier Go-based interactive filtering tool that predates fzf and inspired parts of its design.
PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add unit tests for src/ansi.go to cover edge cases in ANSI escape sequence parsing
The file src/ansi_test.go exists but ANSI parsing is notoriously tricky. Real-world terminal output includes complex sequences like OSC hyperlinks, 256-color and truecolor codes, nested SGR parameters, and malformed escape sequences. Expanding coverage here directly improves reliability for users piping colored output (e.g. from ls --color, git log --color, rg --color) into fzf. This is a bounded, well-scoped task perfect for a new contributor who can audit src/ansi.go for uncovered code paths.
- [ ] Read src/ansi.go thoroughly and identify all branches/code paths (e.g. OSC sequences, CSI sequences with multiple parameters, truncated/malformed sequences)
- [ ] Open src/ansi_test.go and identify which of those branches lack corresponding test cases
- [ ] Add table-driven tests in src/ansi_test.go for: OSC 8 hyperlink sequences, 256-color foreground/background (38;5;n and 48;5;n), truecolor (38;2;r;g;b), malformed/truncated escape sequences, and sequences with multiple semicolon-separated SGR parameters
- [ ] Run
go test ./src/... -v -run TestAnsiand confirm new tests pass and coverage increases - [ ] Submit PR referencing specific uncovered lines in src/ansi.go
Add unit tests for src/cache.go to validate cache eviction and concurrency safety
src/cache_test.go exists but fzf's cache is a performance-critical component used during incremental search. Cache correctness under concurrent access (fzf uses goroutines heavily as seen in src/core.go) and correct eviction behavior are essential. A bug here causes stale results silently. A new contributor can audit src/cache.go for untested concurrency paths and add race-detector-enabled tests, which is a concrete, high-value addition.
- [ ] Read src/cache.go to understand the cache structure, key types, and any locking mechanisms used
- [ ] Review src/cache_test.go to identify missing test cases (e.g. concurrent reads/writes, cache misses, cache hits after mutation, boundary conditions on cache size)
- [ ] Add tests that run with
go test -raceto expose any data races — usesync/errgrouport.Parallel()to simulate concurrent access patterns matching src/core.go usage - [ ] Add tests for cache invalidation scenarios (e.g. query change causes cache miss, repeated identical queries hit cache)
- [ ] Verify with
go test -race -count=10 ./src/... -run TestCachethat no races are detected across multiple runs - [ ] Document in PR description which specific concurrency scenario was previously untested
Add a Windows CI GitHub Actions workflow (.github/workflows/windows.yml) mirroring the existing linux.yml and macos.yml
The repo has .github/workflows/linux.yml and .github/workflows/macos.yml for automated testing, and install.ps1 confirms Windows is a supported platform. However, there is no windows.yml workflow, meaning Windows-specific regressions (path handling, terminal API differences via golang.org/x/sys, PowerShell install script correctness) go undetected until user bug reports. This is a specific, actionable gap a new contributor can fill by adapting the existing Linux/macOS workflow files.
- [ ] Study .github/workflows/linux.yml and .github/workflows/macos.yml to understand the test/build steps used
- [ ] Create .github/workflows/windows.yml using
runs-on: windows-latestand Go setup mirroring the existing workflows - [ ] Include steps: checkout, set up Go (matching version in go.mod: 1.23.0),
go build ./...,go test ./...with-vflag - [ ]
Good first issues
- The shell/ directory has completion scripts for bash, zsh, and fish but shell/update.sh suggests they need periodic syncing — auditing and documenting that update process would be valuable. 2. src/actiontype_string.go is auto-generated but there may be missing or underdocumented actions in doc/fzf.txt (man page) — cross-referencing all action types against the man page and filing gaps is a concrete, bounded task. 3. The Dockerfile exists but likely isn't documented in README.md or BUILD.md — adding a 'building/running via Docker' section to BUILD.md would help contributors on non-Go systems.
Top contributors
- @junegunn — 79 commits
- @bitraid — 6 commits
- @dependabot[bot] — 2 commits
- @Vulcalien — 1 commits
- @TymekDev — 1 commits
Recent commits
b4a86a9— Preserve wrap state across change-preview-window (junegunn)6fefe02— 0.72.0 (junegunn)f783582— Rename internal variables to match user-facing names (junegunn)af42fde— Fix rubocop errors (junegunn)27dab24— Change the example command in the 0.72.0 changelog (junegunn)56be412— Redraw when change-footer changes line count (junegunn)7782da6— Add dashed border style (junegunn)43f0508— Update CHANGELOG (0.72.0) (junegunn)1986d10— Redraw when change-header changes line count (junegunn)dd7a081— Let inline sections take precedence over --header-first (junegunn)
Security observations
- Medium · Dockerfile runs as root user —
Dockerfile. The Dockerfile does not define a non-root USER instruction. All processes inside the container, including the test runner, execute with root privileges. If any part of the application or test suite is compromised, the attacker has full root access within the container. Fix: Add a non-root user and switch to it before running the application:RUN useradd -m fzfuser && chown -R fzfuser /fzffollowed byUSER fzfuser. - Medium · Shell completion and key-binding scripts sourced without integrity verification —
install, shell/. The shell scripts under shell/ (completion.bash, key-bindings.bash, completion.zsh, key-bindings.zsh, completion.fish, key-bindings.fish) are installed via./install --allinside the Docker image and are designed to be sourced directly into user shell sessions. If these files are tampered with (e.g., via a supply-chain attack or MITM during download), arbitrary code could be executed in the user's shell context. There is no checksum or signature verification for these files during installation. Fix: Provide cryptographic checksums (e.g., SHA-256) or GPG signatures for release artifacts. The install script should verify integrity before sourcing or executing any downloaded content. - Medium · Command injection risk via shell preview and execute bindings —
bin/fzf-preview.sh, shell/key-bindings.bash, shell/key-bindings.zsh, shell/key-bindings.fish. fzf supports --preview, --bind, and --execute options that pass user-controlled or externally-supplied strings to a shell. The bin/fzf-preview.sh script and shell key-binding scripts construct shell commands using unquoted or interpolated variables. If fzf is used in scripts where input is not sanitized before being passed to these options, an attacker controlling input items could achieve command injection. Fix: Ensure all variables interpolated into shell command strings within preview/execute scripts are properly quoted. Document clearly in security guidance that user-supplied data passed to --preview or --execute should be treated as untrusted and sanitized by the calling script. - Medium · install.ps1 and install scripts download binaries over the network without strong integrity checks —
install, install.ps1. The install and install.ps1 scripts are responsible for downloading fzf binaries from GitHub releases. If the download is performed without verifying the cryptographic hash of the downloaded binary against a known-good value embedded in the script, a MITM attack or a compromised GitHub release could result in the execution of a malicious binary. Fix: Embed expected SHA-256 checksums for each release binary directly in the install scripts and verify them before executing any downloaded file. Consider also verifying GPG signatures. - Low · Dockerfile apt package installation without pinned versions —
Dockerfile. The Dockerfile runsapt-get update && apt install -y git make golang zsh fish tmuxwithout pinning specific package versions. This means the build is not reproducible and could silently pull in updated packages with vulnerabilities or breaking changes. Fix: Pin package versions in the apt install command (e.g.,golang=2:1.xx) and consider using a lockfile or reproducible base image to ensure deterministic builds. - Low · Sensitive email address exposed in SECURITY.md —
SECURITY.md. The SECURITY.md file contains a maintainer's personal email address (junegunn.c AT gmail DOT com) for security disclosures. While intentional, this may expose the maintainer to spam or social engineering attacks. Fix: Consider using GitHub's private vulnerability reporting feature (Security Advisories) as the primary disclosure mechanism to avoid exposing personal contact information. - Low · No image digest pinning for base Docker image —
Dockerfile. The Dockerfile usesFROM rubylang/ruby:3.4.1-noblewith a version tag but without a SHA256 digest pin. Tags are mutable and the underlying image could be replaced with a malicious one. Fix: Pin the base image to a specific immutable digest, e.g.,FROM rubylang/ruby:3.4.1-noble@sha256:<digest>, to ensure reproducibility and prevent tag hijacking. - undefined · undefined —
undefined. undefined Fix: undefined
LLM-derived; treat as a starting point, not a security audit.
Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.