Hacks
Real fixes for real problems — the dev kind and the life kind — with the failed attempts left in.
Scope your GitHub Actions token before it can write to ev...
Every workflow gets a token that, by default, can push to main. Here's the two-line block that ta...
Threat-model your dotfiles: what a stolen laptop actually...
Your .zshrc is not a nation-state asset. Your ~/.aws/credentials, sitting next to it in plaintext...
STRIDE your side project in 20 minutes: one diagram, six ...
One data-flow arrow, the six STRIDE questions, and a sort command that hands you your three most ...
Scope the token before your MCP server gets the keys: a f...
The easy token is the dangerous one. Hand your agent a classic PAT and one bad prompt can force-p...
Read the query plan before you add the index: EXPLAIN ANA...
Your query is slow, so you add an index on a column that felt right. The query is still slow. You...
Read a failing CI run from your terminal: gh run --log-fa...
The Actions web UI makes you click a run, click a job, and scroll a green log to find the one red...
One mkdir, many folders: brace expansion (and the space t...
mkdir -p proj/{src,test,docs} builds the whole tree in one shot. Put one space after a comma and ...
chmod 644 and 755, never 777: the permission bits that de...
The 777 that 'fixes' a permissions error is a footgun with a delay on it. Here's how to read the ...
Run type -a before you alias, so you don't shadow a comma...
Before you paste alias ls='ls --color' into your .bashrc, ask the shell what 'ls' already means. ...
Rotate the secret you already deleted: the key still livi...
You found an API key in a committed file, deleted the line, and committed the fix. The key is sti...
Read a file's first 16 bytes: walk a magic number with hexyl
Every file starts with a tiny signature that says what it is. Here's how to read it in the shell ...
Count what happens most: the sort | uniq -c tally (and wh...
Which IP hammered the server? Which word repeats? There's a three-stage pipeline for that — and i...
rsync without nuking the wrong directory: the trailing sl...
One character — a trailing slash — decides whether rsync copies a directory or its contents. Add ...
Stop shipping tarballs that explode: archive the director...
A tarball made with `tar czf x.tar.gz *` has no top-level folder, so it scatters loose files into...
Let git find the commit that broke it: git bisect run
'It worked last week' is a bug report with a timestamp. git bisect turns 400 commits into ~9 test...
Undo almost anything in git: the reflog is your undo history
You ran git reset --hard, your commits vanished, and your stomach dropped. They're not gone. git ...
Stop stashing to switch branches: check out two at once w...
You're mid-change and someone needs a hotfix on main. The reflex is stash, switch, fix, switch ba...
Pipe find into xargs without splitting your filenames in ...
find | xargs is the first thing you reach for and the first thing that betrays you the day a file...
Make your bash scripts clean up after themselves: trap … ...
A script that makes a temp dir and then errors out leaves the temp dir behind. One trap line fixe...
Make bash fail loudly: the set -euo pipefail header
By default bash shrugs off a failed command and keeps going. Three flags change that — and then b...
Stop forgetting your own build commands: a Makefile as yo...
Type make help, see every command the project knows — plus the two failures that make people quit...
WSL2: a real Linux dev box on Windows in about ten minutes
One command installs a real Linux kernel on Windows. Here's the command, the in-distro check, and...
Stop typing IP addresses: the ~/.ssh/config block that na...
Name your servers once, type ssh web1 forever — plus the ordering gotcha that silently logs you i...
Version-control your CV: LaTeX, VS Code, and the .gitigno...
Your CV in LaTeX, in git — minus the dozen build files every compile spits out and the one orderi...
Make tmux survive a reboot, not just a disconnect
The survival subset keeps a session alive while you walk away. This is the sequel: getting the la...
One .tmux.conf line per real annoyance: the config that g...
The six-line survival config gets you living in tmux. These are the next few lines — each one ear...
Four fzf shell functions you'll actually keep: kill, bran...
fzf's whole trick is list | fzf | act. Here are four functions that turn that trick into muscle m...
fzf -m: the multi-select functions, and the one time you ...
Add -m and fzf hands back several lines instead of one. That changes the rule we spent the last h...
tmux in 9 commands: the survival subset
tmux has a hundred-page manual. You need nine commands and a six-line config. Here they are, in t...
Make your shell remember where you were (without a produc...
Three lines of shell beat a $9/month developer-workflow app. The bar was on the floor.
Stop retyping the same 12 git commands: a .gitconfig alia...
Ten aliases, real keystroke math, and one cautionary tale about force-push.
When a Yanked FFI Gem Breaks Your Jekyll Docker Build: On...
The container died mid-`bundle install` on an ffi build that no longer exists. One bundle lock co...
Two GitHub Actions workflows that lint, test, and package...
Two YAML files take your extension from push to Marketplace — plus the headless-display crash tha...
Stop fighting Excel: grep, awk, and a month-end close for...
Excel truncates at a million rows. grep and awk don't care how big the file is — here's the month...
Docker from zero: the dozen commands that actually get a ...
Skip the 30-minute tutorial. Here are the twelve commands you'll actually type, plus the daemon e...
Prompt patterns that survive a code review: RCTF, few-sho...
Treat prompts like code: a file, a structure, a check. Plus the one-character path mistake that m...
Your first React app with Vite: components, props, and a ...
Four commands to a running React app, a counter that actually counts, and the two error messages ...
Wrap your scary shell scripts in Gum: a three-layer 'glas...
A gum menu makes deploy.sh hard to fat-finger. The trick is keeping the dangerous part underneath...
Stop 'works on my machine': anchor every script to its ow...
A script that works from the repo root and explodes from a subdirectory isn't flaky — it's readin...
The work/ directory pattern: cache inputs, regenerate out...
CI cached the whole work/ folder to go faster. It went faster and shipped last week's build. Here...
Auto-deploy a Jekyll site over FTP from CI (the Travis re...
FTP-only host, GitHub repo, no money for a fancy pipeline. The CI recipe that bridges them — and ...
Host a Site Free on GitHub Pages: Repo, Jekyll _config, a...
Push a folder, get a website — plus the apex DNS records and the one-line CNAME file everyone fil...
Kill dead links in Jekyll: stable permalinks, redirect_fr...
Rename a post, keep the old link working, and let CI catch the 404 before a reader does.
Fork, build, and run a VS Code extension locally: the Fro...
Get the Front Matter extension building and reloading on your machine — and find out why the guid...
The gh CLI doesn't get GITHUB_TOKEN for free: set GH_TOKE...
GitHub Actions hands every job a token. It does not hand that token to the gh CLI. Here's the one...
It works on macOS, breaks in CI: bash 3.2 vs declare -A
macOS still ships bash 3.2. CI runs bash 5. The gap is associative arrays — here's the failure an...
Stop copy-pasting bash logging: a sourced lib with a relo...
One log.sh, sourced everywhere — plus the readonly crash that happens the second two files pull i...
jq's // default silently eats your false values
You set backup_files to false. jq read it back as true. The // operator is the culprit, and the f...
Why your CI script hangs forever: wrap --version in timeout
The script worked locally and hung in CI. The fix is one word — timeout — plus the set -e gotcha ...
New Django project to first GitHub push, without committi...
Five commands to a Django project on GitHub, and the .gitignore that stops you from publishing yo...
Stop the KeyError: defensively parsing tool JSON in Python
Your script read error['status']['details'] for a year. Then the tool moved the field, and CI die...
A reusable GPT-4o system prompt for dark-to-hopeful satir...
Stop pasting a wall of instructions into the chat box. Put the prompt in a file, build the reques...
Tangle a markdown file into runnable scripts, one file pe...
Your setup doc already contains every command. Stop copy-pasting them one block at a time — extra...
A searchable, sortable Jekyll sitemap from your collectio...
One Liquid loop plus forty lines of vanilla JS turns your collections into a filterable, sortable...
Build a Jekyll sidebar that lists a collection's files an...
Group a collection's docs by folder in a sidebar — and find out why the obvious pop trick prints ...
Document Scraping in Python: BeautifulSoup, Playwright, a...
Most data worth having is locked in HTML that was never meant to be queried. Here's the scraper, ...
Front Matter Placeholders: Stop Frontmatter Drift Before ...
Templates tell authors which fields exist. Placeholders fill them in correctly — and grep finds t...
Scaffold a VS Code extension in five minutes: yo code, F5...
The whole loop from empty folder to a working command in the palette — and the one-line mismatch ...
A robots.txt That Actually Works on Jekyll (and the sitem...
Your robots.txt looks right and crawlers still read it wrong — because Jekyll never rendered the ...
A Jekyll Dockerfile That Builds on Ruby 2.7: Pinning Past...
The fix for 'works on the new Ruby' isn't upgrading. It's pinning the four gems that quietly requ...
Hide Your Navbar on Scroll Down, Bring It Back on Scroll Up
Reclaim the top of the viewport on scroll down, get the nav back on scroll up — and the one-pixel...