Shell Productivity: tmux, fzf & direnv
Keep long go test runs alive in tmux, jump to packages with fzf, and load per-module Go settings with direnv in monorepos.
Search across all documentation pages
Keep long go test runs alive in tmux, jump to packages with fzf, and load per-module Go settings with direnv in monorepos.
Go monorepos spend more time in the terminal than many web stacks: module roots, multiple go.mod trees, and CI-parity test commands.
tmux, fzf, and direnv reduce friction so you spend attention on failures, not on lost sessions or wrong GOPRIVATE values.
Quick-reference recipe card - copy-paste ready.
# direnv: auto-load env when entering a module
echo 'export GOTOOLCHAIN=auto' >> .envrc
echo 'export GOPRIVATE=github.com/myorg/*' >> .envrc
direnv allow
# fzf: fuzzy-find a package directory
fd -t d . internal | fzf --preview 'ls {}'
# fzf + ripgrep: jump to symbol
rg -l "func HandleHealth" --glob '*.go' | fzf -m | xargs -r ${EDITOR:-vim}
# tmux: named session for long test run
tmux new -s gotest
go test ./... -race -count=1 2>&1 | tee test.log
# detach: Ctrl-b d ; reattach: tmux attach -t gotestWhen to reach for this:
go test ./... run.go.mod roots daily.#!/usr/bin/env bash
# ~/.config/dotfiles/go-monorepo-setup.sh (illustrative)
# 1) direnv in a service module
cd ~/src/myorg/platform/services/api
cat > .envrc <<'EOF'
export GOTOOLCHAIN=auto
export GOPRIVATE=github.com/myorg/*
export PATH="$(go env GOPATH)/bin:$PATH"
layout go
EOF
direnv allow
# 2) fzf keybinding: open file from repo (requires fzf installed)
export FZF_DEFAULT_COMMAND='rg --files --hidden --glob "!vendor/**" --glob "!*.git/*"'
open_go_file() {
local file
file=$(fzf -m --preview 'bat -n --color=always {} 2>/dev/null || head -100 {}') || return
${EDITOR:-vim} $file
}
# 3) tmux session with windows for watch + tests
tmux new-session -d -s api -n shell
tmux send-keys -t api:shell "cd ~/src/myorg/platform/services/api && direnv allow ." C-m
tmux new-window -t api -n test
tmux send-keys -t api:test "go test ./... -count=1 -race 2>&1 | tee ~/artifacts/api-test.log" C-m
tmux attach -t apiWhat this demonstrates:
.envrc with GOPRIVATE and layout go for module-aware paths.go test package paths) with optional preview commands.cd and exports variables from .envrc after direnv allow, preventing leaked GOPRIVATE across unrelated projects.| Tool | Solves | Typical Go command |
|---|---|---|
| tmux | Session persistence | go test ./... overnight |
| fzf | Fuzzy selection | pick internal/billing package |
| direnv | Per-directory env | GOTOOLCHAIN, GOPRIVATE |
| ripgrep + fzf | Code navigation | jump to handler definition |
fd | Fast file lists | feed fzf with directories |
# .envrc
use go # if available in direnv stdlib
export GOTOOLCHAIN=auto
export GOPRIVATE=*.corp.example.com,github.com/myorg/*
export GOLANGCI_LINT_CACHE=$PWD/.cache/golangci-lint
PATH_add "$(go env GOPATH)/bin"Run direnv allow once per directory after editing .envrc.
# fzf: pick a package and test it
go list ./... | fzf -m | xargs -r go test -count=1Combine with go list -json for module path metadata in advanced previews.
direnv allow - .envrc changes silently do nothing. Fix: alias da='direnv allow' and hook shell prompt to show direnv status.set -g mouse on in ~/.tmux.conf and learn copy mode (Ctrl-b [)..gitignore respect - Feeding plain find includes vendor noise. Fix: rg --files or fd with ignore rules..bashrc applies org-wide to personal projects. Fix: direnv per repo only.tmux list-sessions and name by service (api, worker).head or bat --line-range 1:80.| Alternative | Use When | Don't Use When |
|---|---|---|
| tmux | SSH remote dev | Local IDE integrated terminal only |
| GNU screen | tmux unavailable | Greenfield - prefer tmux features |
| IDE workspace per module | Rich refactor tools | Minimal remote VM |
just / Make tasks | Repeatable team commands | Exploratory fuzzy navigation |
| Zellij | Modern multiplexer UX | Team already standardized on tmux |
Remote SSH recovers editor state but long purely-shell test runs still die if the remote connection drops without tmux.
It sets GOPATH layout conventions for legacy GOPATH workflows.
Module mode projects mostly need PATH_add and env exports, not full GOPATH src layout.
fzf-cd-widget (zsh) or custom bash bindings jump directories quickly.
Pair with zoxide for frequent paths.
Default Ctrl-b is fine.
Some maps prefix to Ctrl-a for Emacs users - document team config in dotfiles repo.
tmux sockets and SSH multiattach are possible but rare.
Prefer shared logs in artifacts over shared interactive sessions.
Yes with direnv's fish hook.
Go env vars apply the same once allowed.
git branch | fzf | xargs git checkout is a common binding.
Useful when release branches multiply.
Yes for non-secret defaults.
Never commit secrets - reference 1Password or vault paths instead.
Yes.
watchexec or entr on *.go pairs well with a lint pane beside go test watch.
apt install tmux fzf direnv ripgrep fd-find (binary may be fdfind).
macOS: Homebrew equivalents.
One pane owns the long ./... run; others run scoped go test ./internal/pkg -run TestName.
No, but it pays off when go list ./... returns hundreds of packages.
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).
Reviewed by Chris St. John·Last updated Jul 18, 2026