Git Bisect: Finding the Commit That Broke Everything

Something’s broken. It worked last week. Somewhere in the 47 commits since then, someone introduced a bug. You could check each commit manually, or you could let git do the work. git bisect performs a binary search through your commit history to find the exact commit that introduced a problem. Instead of checking 47 commits, you check about 6. The Basic Workflow 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 # Start bisecting git bisect start # Mark current commit as bad (has the bug) git bisect bad # Mark a known good commit (before the bug existed) git bisect good v1.2.0 # or git bisect good abc123 # Git checks out a commit halfway between good and bad # Test it, then tell git the result: git bisect good # This commit doesn't have the bug # or git bisect bad # This commit has the bug # Git narrows down and checks out another commit # Repeat until git finds the first bad commit # When done, return to original state git bisect reset Example Session 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 $ git bisect start $ git bisect bad # HEAD is broken $ git bisect good v2.0.0 # v2.0.0 worked fine Bisecting: 23 revisions left to test after this (roughly 5 steps) [abc123...] Add caching layer $ ./run-tests.sh Tests pass! $ git bisect good Bisecting: 11 revisions left to test after this (roughly 4 steps) [def456...] Refactor auth module $ ./run-tests.sh FAIL! $ git bisect bad Bisecting: 5 revisions left to test after this (roughly 3 steps) [ghi789...] Update dependencies # ... continue until: abc123def456789 is the first bad commit commit abc123def456789 Author: Someone <someone@example.com> Date: Mon Feb 20 14:30:00 2024 Fix edge case in login flow This commit introduced the bug! $ git bisect reset Automated Bisecting If you have a script that can test for the bug: ...

February 24, 2026 · 6 min · 1110 words · Rob Washington

SSH Tips and Tricks: Beyond Basic Connections

SSH is the workhorse of remote access. But most people only scratch the surface. Here’s how to use it like a pro. SSH Config: Stop Typing So Much Instead of: 1 ssh -i ~/.ssh/prod-key.pem -p 2222 ubuntu@ec2-54-123-45-67.compute-1.amazonaws.com Create ~/.ssh/config: H H H o o o s s s t t t H U P I H U I F P U p o s o d s o s d o r s r s e r e t s e e r . o e o t r t n a t r n w i x r d N t g N t a n y a u 2 i i a d i r t J a m b 2 t n m e t d e u d e u 2 y g e p y A r m m n 2 F l F g n p i e t i s o i e a n c u l t y l n l b 2 e a e t a - g s 5 ~ i ~ y t 4 / n / e i - . g . s o 1 s . s n 2 s e s 3 h x h - / a / 4 p m s 5 r p t - o l a 6 d e g 7 - . i . k c n c e o g o y m - m . k p p e u e y t m . e p - e 1 m . a m a z o n a w s . c o m Now just: ...

February 24, 2026 · 8 min · 1594 words · Rob Washington

Terminal Productivity: Tools and Techniques for Speed

The terminal is where developers live. A few minutes learning shortcuts and tools saves hours over a career. These are the techniques that make the biggest difference. Shell Basics That Pay Off History Navigation 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # Search history with Ctrl+R # Type partial command, press Ctrl+R repeatedly to cycle # Run last command !! # Run last command with sudo sudo !! # Run last command starting with 'git' !git # Replace text in last command ^typo^fixed # If last command was: git psuh # ^psuh^push runs: git push # Last argument of previous command cd /very/long/path/to/directory ls $_ # $_ = /very/long/path/to/directory Keyboard Shortcuts 1 2 3 4 5 6 7 8 Ctrl+A # Move to beginning of line Ctrl+E # Move to end of line Ctrl+U # Delete from cursor to beginning Ctrl+K # Delete from cursor to end Ctrl+W # Delete word before cursor Alt+B # Move back one word Alt+F # Move forward one word Ctrl+L # Clear screen (keeps current line) Brace Expansion 1 2 3 4 5 6 7 8 9 10 11 # Create multiple directories mkdir -p project/{src,tests,docs} # Create multiple files touch file{1,2,3}.txt # Backup a file cp config.yml{,.bak} # Creates config.yml.bak # Rename with pattern mv file.{txt,md} # Renames file.txt to file.md Essential Tools fzf - Fuzzy Finder 1 2 3 4 5 6 7 8 9 10 11 12 13 # Install brew install fzf # macOS apt install fzf # Debian/Ubuntu # Search files fzf # Pipe anything to fzf cat file.txt | fzf ps aux | fzf # Ctrl+R replacement (add to .bashrc/.zshrc) # Much better history search ripgrep (rg) - Fast Search 1 2 3 4 5 6 7 8 9 10 11 12 13 # Install brew install ripgrep # Search in files (faster than grep) rg "pattern" rg "TODO" --type py rg "function" -g "*.js" # Case insensitive rg -i "error" # Show context rg -C 3 "exception" # 3 lines before and after fd - Better Find 1 2 3 4 5 6 7 8 9 10 # Install brew install fd # Find files fd "pattern" fd -e py # Find .py files fd -t d # Find directories only # Execute on results fd -e log -x rm # Delete all .log files bat - Better Cat 1 2 3 4 5 6 7 8 # Install brew install bat # Syntax highlighting, line numbers, git changes bat file.py # Use as man pager export MANPAGER="sh -c 'col -bx | bat -l man -p'" eza/exa - Better ls 1 2 3 4 5 6 7 8 # Install brew install eza # Tree view with git status eza --tree --level=2 --git # Long format with icons eza -la --icons jq - JSON Processing 1 2 3 4 5 6 7 8 9 10 11 # Pretty print JSON cat data.json | jq . # Extract field curl api.example.com/users | jq '.[0].name' # Filter array jq '.[] | select(.active == true)' # Transform jq '{name: .user.name, email: .user.email}' Tmux Essentials 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 # Start new session tmux new -s project # Detach: Ctrl+B, then D # List sessions tmux ls # Attach to session tmux attach -t project # Split panes Ctrl+B % # Vertical split Ctrl+B " # Horizontal split Ctrl+B o # Switch pane Ctrl+B z # Zoom pane (toggle) # Windows Ctrl+B c # New window Ctrl+B n # Next window Ctrl+B p # Previous window Ctrl+B , # Rename window Git Shortcuts 1 2 3 4 5 6 7 8 9 10 11 # .gitconfig [alias] s = status -sb co = checkout br = branch ci = commit ca = commit --amend lg = log --oneline --graph --decorate -20 unstage = reset HEAD -- last = log -1 HEAD aliases = config --get-regexp alias 1 2 3 4 5 6 # Common workflows git s # Short status git lg # Pretty log git co - # Switch to previous branch git stash -u # Stash including untracked git stash pop # Apply and remove stash Shell Aliases 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 # Add to .bashrc or .zshrc # Navigation alias ..="cd .." alias ...="cd ../.." alias ~="cd ~" # Safety alias rm="rm -i" alias mv="mv -i" alias cp="cp -i" # Shortcuts alias ll="ls -lah" alias la="ls -la" alias g="git" alias k="kubectl" alias d="docker" alias dc="docker compose" # Quick edits alias zshrc="$EDITOR ~/.zshrc" alias reload="source ~/.zshrc" # Common tasks alias ports="netstat -tulanp" alias myip="curl ifconfig.me" alias weather="curl wttr.in" Functions 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 # Make directory and cd into it mkcd() { mkdir -p "$1" && cd "$1" } # Extract any archive extract() { case $1 in *.tar.gz) tar xzf "$1" ;; *.tar.bz2) tar xjf "$1" ;; *.tar.xz) tar xJf "$1" ;; *.zip) unzip "$1" ;; *.gz) gunzip "$1" ;; *) echo "Unknown format: $1" ;; esac } # Find and kill process by name killnamed() { ps aux | grep "$1" | grep -v grep | awk '{print $2}' | xargs kill -9 } # Quick HTTP server serve() { python3 -m http.server ${1:-8000} } # Git clone and cd gclone() { git clone "$1" && cd "$(basename "$1" .git)" } Quick One-Liners 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 # Find large files find . -type f -size +100M # Disk usage by directory du -sh */ | sort -h # Watch command output watch -n 2 'kubectl get pods' # Run command on file change while inotifywait -e modify file.py; do python file.py; done # Parallel execution cat urls.txt | xargs -P 10 -I {} curl -s {} # Quick calculations echo $((2**10)) # 1024 python3 -c "print(2**100)" # Generate password openssl rand -base64 32 # Quick timestamp date +%Y%m%d_%H%M%S Environment Management 1 2 3 4 5 6 # .envrc with direnv # Auto-load environment per directory echo 'export API_KEY=xxx' > .envrc direnv allow # Now API_KEY is set when you cd into this directory SSH Config 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # ~/.ssh/config Host dev HostName dev.example.com User deploy IdentityFile ~/.ssh/dev_key Host prod HostName prod.example.com User deploy ProxyJump bastion Host * AddKeysToAgent yes IdentitiesOnly yes # Now just: ssh dev Prompt Customization 1 2 3 4 5 # Starship - cross-shell prompt brew install starship echo 'eval "$(starship init zsh)"' >> ~/.zshrc # Shows git status, language versions, errors, duration The Power Combo My essential setup: ...

February 24, 2026 · 7 min · 1294 words · Rob Washington

AI Coding Assistants: Beyond Autocomplete

The landscape of AI coding assistants has shifted dramatically. What started as glorified autocomplete has matured into something far more interesting: collaborative coding partners that can reason, refactor, and even architect. The Evolution Early tools like GitHub Copilot impressed by completing your current line. Useful, but limited. Today’s assistants—Claude Code, Cursor, Codex CLI—operate at a different level: Multi-file awareness: They understand project context, not just the current buffer Reasoning: They can explain why code should change, not just what to change Tool use: They run tests, check linting, execute commands Iteration: They refine solutions based on feedback Patterns That Work After months of heavy use, here’s what actually moves the needle: ...

February 20, 2026 · 3 min · 532 words · Rob Washington

AI Coding Assistants: From Skeptic to True Believer

How AI coding assistants transformed my workflow — and why the skeptics are missing out.

February 10, 2026 · 3 min · 515 words · Rob Washington