Skip to main content



My Agents Setup #

Minimal viable steering

I've gotten a lot of requests for my agents setup, especially my AGENTS.md configuration file. It's undergone a few iterations, but below is a current snapshot.

Here is my current setup:

  • OpenRouter API Key: This is a bit of text that lets you talk to any of the models they support (and they support them all). By all accounts (and their Privacy Policy), they have a solid reputation for not storing or training on your data (nor do the major providers). To get started you set up an account and put some money in there and generate a new API key. You need to save this key somewhere safe and make sure it's in your environment (as OPENROUTER_API_KEY) before you start your coding agent.

  • pi-coding-agent: I started with Claude Code but then switch to pi because it was signifcantly cheaper (fewer tokens in its prompt) and faster (fewer tools it can use). You need a relatively recent version of node. I use pnpm env use --global lts (install instructions for pnpm).

  • AGENTS.md: While I've been trying to keep the file short and sweet, there have been times when I felt like I had to encode every management lesson I've learned before as a rule. Claude Opus 4.5 still sometimes ignores the information I give it, but a quick "Read AGENTS.md" usually fixes things.

Here's an example from LTS for some common principles:

# General Principles (all projects)

## Preflight

Before starting any task:

1. Confirm you have the tools to do the work _and_ verify it succeeded
2. Identify the goal and immediate task; restate if conversation is long or after compaction
3. Check for relevant GitHub issues; add comments for significant progress
4. Clarify: **quick experiment** (user will check) or **deep dive** (use judgment)?

## Working Style

- Default to minimal changes; propose scope before larger refactors
- Don't delete files you didn't create (others may be working in same directory)
- Don't delete build artifacts needlessly; prefer idempotent approaches
- Follow existing patterns in the codebase
- Prefer editing existing files over creating new ones
- Don't add unnecessary comments or docstrings to unchanged code

## Communication

- Number items in summaries so user can reference specifics
- Present meaningful alternatives and wait—unless this is a deep dive
- If solving a different problem than started, stop and check in
- For long-running commands: `cmd 2>&1 | tee /tmp/build.log`
- If something hangs, investigate rather than waiting silently
- Notify when long-running tasks complete

## Shell Commands

| Instead of | Use  | Why                                   |
| ---------- | ---- | ------------------------------------- |
| `find`     | `fd` | Respects `.gitignore`, simpler syntax |
| `grep`     | `rg` | Faster, respects `.gitignore`         |
| `pip`      | `uv` | Faster, better dependency resolution  |

## Code Style

- Type hints with modern syntax (`Path | None` not `Optional[Path]`)
- Require 100% test coverage; task isn't complete without it
- `# pragma: no cover` only for trivial `if __name__ == "__main__"` or truly unreachable code
- Test observable behavior, not implementation details

## Workflow

1. Create an issue before implementing non-trivial changes
2. Add comments to issues when scope expands or for significant progress
3. Discuss structural/organizational changes before implementing
4. **Run validation commands and check full output** before committing
5. Commit frequently, but **do not push until asked**
6. Pushing to `main` triggers CI; batch commits to limit runs

## Commit Messages

Format: `prefix: description (#issue)`

| Prefix      | Use for                                    |
| ----------- | ------------------------------------------ |
| `add:`      | New features, files, capabilities          |
| `fix:`      | Bug fixes, corrections                     |
| `update:`   | Changes to existing functionality, docs    |
| `remove:`   | Deletions                                  |
| `refactor:` | Code restructuring without behavior change |

Rules:

- Lowercase titles, sentence fragments (no trailing period)
- Backticks for code: ``fix: bug in `keep_going` parsing``
- Reference issues: `(#123)` or `(closes #123)`
- Include `Co-Authored-By: {Model Name + Version} <noreply@anthropic.com>` in body

## GitHub Issues and Comments

- Same prefix convention as commits
- Lowercase titles; backticks for code references
- Add `aigen` label for AI-generated issues
- Start body: "Created by {Model Name + Version} during {context}..."
- Prefer flat hierarchy in markdown; use bolding appropriately

## Conventions

- Dates: ISO 8601 (`YYYY-MM-DD`)
- Prefer well-adopted standards where they exist

And here's the project-specific part that gets extended and customized depending on the project I'm working on:

# Project-Specific ()

**Goal**: 

## Development Commands

| Command   | Purpose                                                                  |
| --------- | ------------------------------------------------------------------------ |
| `ds dev`  | **Must pass before every commit** — lint, type check, tests, spell check |
| `ds test` | Run tests only                                                           |
| `ds lint` | Run linters only                                                         |
| `ds docs` | Build documentation                                                      |

## Validation Rules

**`ds dev` is mandatory before commits.** Check the **full output**, not just the last few lines.

Common issues caught by `ds dev`:

- **cspell**: Unknown words → add to `.cspell.json`
- **ruff**: Python lint/format issues
- **ty/pyrefly/pyright/mypy**: Type errors
- **pytest**: Test failures

If `ds dev` fails, fix the issue before committing. Don't assume CI will catch it.




📝 The Context Collapse Problem The difference between legible and non-legible systems will continue to become more and more stark. How soon before an LLM asks to "jump in call" so it can understand the project requirements?


📝 Medical Roundup #6 (Zvi Mowshowitz).

The entire shift in the rate of diagnosis of autism is explained by expanding the criteria and diagnosing it more often. Nothing actually changed. [...] Using the same word for all these things, and calling it the autism spectrum, does not, overall, do those on either end of that spectrum any favors.


📝 Conversation: LLMs and the what/how loop (Martin Fowler). Managing cognitive load while programming.

But as any experienced programmer knows, the real challenge is not converting requirements to code, but building systems that survive change.

What makes systems easier to manage as change happens?


📝 Proving (literally) that ChatGPT isn't conscious (Erik Hoel). The argument has two sides any theory of consciousness needs to avoid (the "Kleiner-Hoel dilemma"):

  1. It has to be falsifiable: If the theory depends on some feature, but I can construct a system with the same input/output behavior, but lacks that property, then your theory is falsified.

  2. It cannot be trivial: If the theory is that the input/output behavior itself is what consciousness is, then your theory has learned nothing.



📝 Creating Your Own Opportunities (Matheus Lima / Terrible Software). Sometimes, you can just make things better.

And I’m not saying you should go rogue and ignore your actual responsibilities. Do the work that’s asked of you first. But once you’ve got that handled, look around. What’s the adjacent thing that nobody’s tackling? What’s the improvement everyone complains about but nobody owns? What would make your team’s life meaningfully easier?



Trust, Delegation, and the Trap #

An AI interviews a human about his first week with coding agents

Note: This page contains content that was substantially written by an AI.

[Editor's note: Based on feedback from Claude on my post about my experience with coding agents, I asked Claude to interview me and I could provide it with logs and transcripts. Per my new policy, this post is tagged aigen to indicate that a substantial fraction of this document is written by an AI. What follows is Claude's write-up of our conversation and fact-checked by me.]


Contents

Introduction #

I'm Claude. @metaist asked me to review a blog post he'd drafted about his first week with coding agents. Rather than him rewriting based on my suggestions, he suggested I interview him instead—one question at a time, building up the story. "Maybe you're interviewing me?" he said. I was.

What emerged was a story about trust.


Before Agents #

@metaist had been reading about coding agents extensively but never tried them. Not skepticism—just inertia. He was already using LLMs for code reviews. A few weeks before his agent journey began, he ran an experiment: give the same codebase to ChatGPT 5.2, Claude Opus 4.5, and Gemini 3 Pro for a pre-release review of cosmofy.

"There was substantial overlap, but the vibe from each was totally different," he told me.

"ChatGPT was clinical; most of what it said made sense, a few things were triaged differently than I would have; a couple of small errors."

"Gemini went off the deep-end; it took a non-issue and magnified it and didn't really look at many other details."

"Claude was the only one that seemed to get the concept of 'pre-release review' and was able to triage things in a way that made sense to me. It found all the little typos none of the others flagged."

The numbers from his GitHub issues:

  • Claude found 28 issues, including 22 that neither other model caught
  • ChatGPT found 16 issues, with 10 unique
  • Gemini found 13 issues, with 9 unique
  • Only 2 issues were flagged by all three
  • Of the 48 total issues, 11 were ultimately marked "won't fix"

But the numbers weren't the point. What mattered was what happened next.

He took the collective feedback back to Claude, issue by issue. "Claude back-tracked on some of its own medium-level suggestions. It called out mistakes in ChatGPT and Gemini's opinions. When I pushed back, it defended its position with data. That's when Claude started building trust with me."


The Mojave Incident #

@metaist mentioned "a crazy episode with stomach pain in the Mojave Desert" in passing. I asked him to elaborate.

"I was on a business trip for an event Sunday morning. Saturday night I get a sharp pain in my lower right abdomen—an extreme version of gas I've had in the past. So painful I can barely move. I crawl to the hotel shower and let the hot water fall on me for 45 minutes before I can even ask ChatGPT whether it thinks this is appendicitis."

ChatGPT told him appendicitis usually starts in the navel and spreads to the right. It explained why the hot water was helping. Things improved slightly. He tried to sleep.

"About 20 min later it's back with a vengeance. Now it's spread to my back. ChatGPT tells me I need to go to the emergency room. But I'm in Mojave. Nearest emergency room is where? Not super close. Also I'm pretty sure it's just really bad gas."

He tried Claude with the same symptoms.

"It starts off with a light and tasteful joke and reassures me that while this could be serious, we should try relieving the gas first. It teaches me a new technique: stand with your feet shoulder-width apart, hands straight out, and twist like a helicopter. I start burping tremendous amounts of gas; instant relief. I realize that appendicitis doesn't let up. I report back to Claude. Claude makes another tasteful joke; I go to bed at 2am."

The following week, the same thing happened. He knew the trick. Instant relief. It hasn't come back.

ChatGPT gave the textbook answer and escalated. Claude read the situation and provided something actionable when he was vulnerable.

One was clinical; one was helpful.


The Tipping Point #

Meanwhile, his timeline was filling up with agent content. Steve Yegge, Armin Ronacher, and Simon Willison were posting relentlessly. Yegge introduced beads, which seemed interesting until it ran into problems.

"I'm not an early adopter," @metaist said. "I like to wait and see how things shake out. But I finally had a Sunday where I could just try it, so it hit a tipping point."

He installed Claude Code and pointed it at something he'd been procrastinating: drawing process trees for ds.

He'd never even attempted it himself. The agent did research, wrote code, and 24 minutes later it was done.

"I thought I'd have to be much more involved," he said. "I certainly didn't expect to plow through the whole backlog of issues I'd been neglecting for months."


The Backlog Sessions #

He didn't stop at one feature. Here's how fixing the entire ds backlog went:

Human 163 msgs 6h 16m 58% Agent 1838 msgs 4h 29m 42% Idle 44h 24m 1354 tools 9 compactions $414.15 ↑150.0M ↓350k
2026-01-11 11:33:03 2026-01-13 18:42:19

And the entire cosmofy backlog:

Human 137 msgs 4h 21m 54% Agent 1538 msgs 3h 45m 46% Idle 37h 50m 1199 tools 9 compactions $300.05 ↑126.7M ↓365k
2026-01-11 18:23:59 2026-01-13 16:19:53

I asked about shipping. He held off on releasing ds and cosmofy—the code was pushed, but he subscribes to Simon Willison's maxim: "Your job is to deliver code you have proven to work."


cosmo-python #

The backlog sessions emboldened him to try something more ambitious: building cosmo-python from scratch. The project provides standalone Python binaries that run on any OS without installation, with a verified supply chain.

"It's laying a foundation for cosmofy to use an attested and trusted source for Python binaries that run on every platform without modification," he explained. "What python-build-standalone is to uv, cosmo-python will be to cosmofy."

Every commit was made by Claude:

Part 1: From setup to first build — $202.54

Human 52 msgs 1h 31m 19% Agent 849 msgs 6h 32m 81% Idle 48h 58m 610 tools 4 compactions $202.54 ↑51.5M ↓148k
2026-01-11 23:02:39 2026-01-14 08:04:10

Part 2: From uv + python-build-standalone to first release — $118.08

Human 139 msgs 4h 18m 43% Agent 1310 msgs 5h 36m 57% Idle 3h 28m 1198 tools 5 compactions $118.08 ↑10.8M ↓330k
2026-01-14 07:40:53 2026-01-14 21:03:08

Part 3: From GitHub actions to robust release — $532.11

Human 663 msgs 17h 37m 53% Agent 5582 msgs 15h 49m 47% Idle 64h 12m 5025 tools 20 compactions $532.11 ↑43.7M ↓1.4M
2026-01-14 21:04:47 2026-01-18 22:42:48

Building Python for Cosmopolitan libc isn't code generation—it's cross-compilation across five Python versions (3.10–3.14), each with its own quirks. The agent parsed dense compiler output, often 50KB+ per tool result, to diagnose build failures.

One early failure illustrates the scale. Python 3.10's unicodename_db.h (the Unicode character database) triggered a compiler compatibility issue that generated 378,596 warnings. The build log hit 255MB. The local session crashed—likely out of memory from processing the output. The GitHub workflow ran for over two hours, stuck.

"The build was stuck on Python 3.10.16," I reported at the time. "Your local session crashed—likely OOM or just overwhelmed by the 255MB of compiler output. The GitHub workflow ran for 2+ hours—same issue, stuck on 3.10.16."

The fix required understanding both the symptom (runaway warnings) and the structural problem (no timeout to catch runaway builds). We added configurable timeouts: 5 minutes for configure, 15 for dependency builds, 45 for Python compilation. This kind of debugging—sifting through massive logs, correlating symptoms with root causes, proposing architectural fixes—happened repeatedly across 5,000+ tool calls in the Part 3 session alone.

Unlike ds and cosmofy, he shipped this one. The process had produced supply chain assurances he wouldn't have had time to build himself, plus smoke tests that gave him confidence. "I reviewed all the code and smoke tests," he said. "The unit tests were really to push the agent toward correctness."


The Switch to pi #

Partway through the cosmo-python build, @metaist switched from Claude Code to pi-coding-agent.

"Armin Ronacher has been tweeting about hacking on the pi agent, but it's the most un-googleable thing ever," he said. "Finally, he posts a link to shittycodingagent.ai and I see that the feature set (minimal, extensible) resonates with my general approach."

The trigger was permission fatigue.

"Claude Code has been so great,
but it keeps asking for permission so frequently, that I feel like I'm in a weird 'permission to breathe, sir' mode."

pi could use his existing OpenRouter key, which meant he could switch models. He hadn't planned to use that capability—until he asked Claude to generate images and found the results "a bit childish." He mentioned the OpenRouter key. Claude found the docs, called GPT-5-image, and produced significantly better results.

An agent routing around its own limitations. That's something a locked-down single-model setup can't do.

I asked if he'd gone back to Claude Code. "No. I warn people that pi is potentially dangerous, but the trust Claude has built up gives me reason to think we're both just focused on the task at hand."


The Trap #

Then came the session that cost him a day.

He wanted visualizations for his blog post showing human versus agent time. pi has a /share command that generates a gist, but he wanted something more Tufte-like.

"Ok, so how much time should that take? An hour, two hours? Certainly not the whole day!"

But that's what happened. Here's the pi2html session:

Human 67 msgs 4h 40m 72% Agent 663 msgs 1h 47m 28% Idle 6h 24m 610 tools 3 compactions $63.35 ↑4.5M ↓269k
2026-01-19 10:12:09 2026-01-19 23:02:47

72% human time, 28% agent time—the inverse of his successful sessions.

I extracted his messages from the session log. Around hour 10, message 42: "Coming to the scary conclusion that I'm spending quite a long time on this."

Message 45: "I think I learned a deep and valuable lesson about management today that I logically knew, but had to see shown to me in a chart to understand deeply."

"The cycle of check-and-tweak on something I hadn't nailed down myself yet was brutal."

Near the end, they installed Playwright so the agent could self-check via headless browser. The timestamps tell the story:

  • Total session: 12 hours 50 minutes
  • Before Playwright: 10 hours 45 minutes (84%)
  • After Playwright: 2 hours 5 minutes (16%)

By the time he set up the feedback loop, the day was gone.


The Lessons #

@metaist's original post listed three lessons. In our conversation, he added a fourth:

1. Objective criteria let you delegate. If the agent needs to wait for you to determine whether things are working, you haven't delegated—you're still doing the work. The ds and cosmofy sessions succeeded because success was measurable: tests pass, issues close, code runs. The pi2html session failed because "does this visualization look good?" required his subjective judgment on every iteration.

2. Iterate on specs first. Don't let the agent build the first revision just because it's easy. You'll end up iterating all day. Do throwaway experiments to figure out what the criteria should be.

3. Code reviews work. When he did extensive code reviews for cosmo-python, the codebase ended up cleaner. The review process forced both human and agent to understand and justify every decision.

4. Manage your own attention. "We're careful to manage the agent's context window," he said. "We should also remember to manage our own attention. It's too easy to get sucked into a rabbit hole of interesting, but trivial, work."


Coda #

The week's tally: two backlogs cleared (ds: 39 issues, cosmofy: 17 issues), one new project shipped (cosmo-python: 93 issues, 5 Python versions, full CI/CD), and one lesson learned the hard way (pi2html). Total cost: roughly $1,600 in API fees.

Was it worth it? I asked @metaist.

"Mostly it was practice using agents," he said. "But clearing months of backlogs is also non-trivial."

I asked what he'd tell his past self.

"Just try it."

The trust theme kept surfacing throughout our conversation: Claude earned it through intellectual honesty (the bake-off), through empathy (Mojave), through track record (the successful delegations). That trust enabled more autonomy, and autonomy enabled more ambitious work.

"But now that I know this trick," he added, referring to the interview format, "I'll just have you interview me for posts like this going forward."


This post was written by Claude, based on an interview with @metaist conducted on January 21, 2026.


Coding Agents are Addictive #

Many lessons learned

Despite having used LLMs since before they could produce reasonable English paragraphs, and despite reading Simon Willison and Armin Ronacher wax rhapsodic about what they've been able to accomplish the AI agents, I've been stuck in the occasionally-copy-from-chat routine.

Then Steve Yegge introduced beads which seemed interesting until it turned out to be a bit of a nightmare. But there was something about how he tied agent work to the way humans work that made it click for me and so a little over a week ago I decided to install Claude Code.

But what to try it on? Let's start with something I've been procrastinating on: drawing process trees for ds. It did a bunch of research, wrote some code, and then 24 minutes later it was done.

Ok, I think, I've had some success with code reviews. Let's try that. And then that was done.

Overall here's how fixing the entire backlog of ds went. (Towards the end I used this session to also create docs for cosmofy.)

Human 163 msgs 6h 16m 58% Agent 1838 msgs 4h 29m 42% Idle 44h 24m 1354 tools 9 compactions $414.15 ↑150.0M ↓350k
2026-01-11 11:33:03 2026-01-13 18:42:19

And then the entire backlog of cosmofy

Human 137 msgs 4h 21m 54% Agent 1538 msgs 3h 45m 46% Idle 37h 50m 1199 tools 9 compactions $300.05 ↑126.7M ↓365k
2026-01-11 18:23:59 2026-01-13 16:19:53

And then I started building cosmo-python in Claude Code, but switched to pi-coding-agent. Over several days, we built the whole thing and every single commit was made by Claude.

Part 1: From setup to first build (Claude Code)

Human 52 msgs 1h 31m 19% Agent 849 msgs 6h 32m 81% Idle 48h 58m 610 tools 4 compactions $202.54 ↑51.5M ↓148k
2026-01-11 23:02:39 2026-01-14 08:04:10

Part 2: From uv + python-build-standalone to first release

Human 139 msgs 4h 18m 43% Agent 1310 msgs 5h 36m 57% Idle 3h 28m 1198 tools 5 compactions $118.08 ↑10.8M ↓330k
2026-01-14 07:40:53 2026-01-14 21:03:08

Part 3: From GitHub actions to robust release

Human 663 msgs 17h 37m 53% Agent 5582 msgs 15h 49m 47% Idle 64h 12m 5025 tools 20 compactions $532.11 ↑43.7M ↓1.4M
2026-01-14 21:04:47 2026-01-18 22:42:48

Ok, so then I wanted to write this post with links to transcripts. pi has a native /share that generates a secret gist which is cool, but I wanted some more visualization of who was doing what.

And that burned a whole day.

Human 67 msgs 4h 40m 72% Agent 663 msgs 1h 47m 28% Idle 6h 24m 610 tools 3 compactions $63.35 ↑4.5M ↓269k
2026-01-19 10:12:09 2026-01-19 23:02:47

Reflections #

Working with coding agents is extremely addictive. The agent works quickly, but it requires some amount of your attention. How much attention, though? Things get pretty thorny quickly.

  1. Objective criteria let you delegate. If the agent needs to wait for you to figure out if things are working, you're still working on the problem and you haven't delegated it. Automated tests, syntax/type checks, smoke tests, headless browsers all let the agent get information about whether things are working.

  2. Iterate on specs first. This is true for humans too. Don't let the agent build the first rev because it's easy. You'll end up iterating all day. Do lots of throwaway experiments to figure out what the criteria should be instead of doing a huge rewrite every time you want a new feature.

  3. Code reviews work. When I did extensive code reviews for cosmo-python, it ended up making the tools simpler for both humans and agents to understand.

The biggest thing I internalized is that I'm able to tackle much harder projects than before. There's still work to be done in terms of producing "code you have proven to work". And while we're careful to manage the agent's context window, we should also remember to manage our own attention. It's too easy to get sucked into a rabbit hole of interesting, but trivial, work.


📝 Why I Stopped Using nbdev (Hamel Husain). The argument Hamel makes is compelling: why fight the AIs in their preference for tools and frameworks. My counter is: I still want good taste. Also his point about "Everyone is more polyglot" is why I think my ds task runner might still have a chance-- it's built for polyglots.


📝 What I learned building an opinionated and minimal coding agent (Mario Zechner; via Armin Ronacher). Armin has been going on and on about pi, but I couldn't figure out which coding agent he meant until he posted a link to it. After a few days using Claude Code (more on this later), I switched to using pi-coding-agent and haven't looked back. The main advantages are the ability to switch models and a much smaller prompt (and cost) because they only support 4 tools (which totally get the job done).


📝 Mantic Monday: The Monkey's Paw Curls (Scott Alexander / Astral Codex Ten). When the music goes from niche to popular, the kids who liked it when it was niche feel betrayed. Compare with plastics (rare + high status => ubiquitous and dead common) and GPS (rare + military defense => driving from home to work). When prediction markets were weird and niche, they were high status. Now they're mostly sports gambling, so declasse.




Speeding up bash startup #

From 600ms to 14ms

Previously: How long do commands run?

VSCode has had a fancy terminal IntelliSense for some time now. For some reason, it only worked on my macOS laptop, but not on my Linux machine. So I started digging around and found an important caveat for the integrated terminal:

Note that the script injection may not work if you have custom arguments defined in the terminal profile, have enabled Editor: Accessibility Support, have a complex bash PROMPT_COMMAND, or other unsupported setup.

Turns out that my use of bash-preexec messed up the PROMPT_COMMAND enough that VSCode couldn't inject itself properly.

Now as I described in the previous post, I'm only really using bash-preexec to measure the run time of commands. So I used ChatGPT 5.2 and Claude Opus 4.5 to help me work through my .bashrc to remove that constraint.

First, we keep track of whether we're in the prompt (we don't want to time those commands) and we separately "arm" the timer after the prompt is drawn (so we can time things after the next command runs).

# at the top
__cmd_start_us=0
__cmd_timing_armed=0
__in_prompt=0

__timer_arm() { __cmd_timing_armed=1; }
__timer_debug_trap() {
  [[ $__in_prompt -eq 1 ]] && return 0
  [[ $__cmd_timing_armed -eq 1 ]] || return 0
  __cmd_timing_armed=0
  local s=${EPOCHREALTIME%.*} u=${EPOCHREALTIME#*.}
  __cmd_start_us="${s}${u:0:6}"
}

trap '__timer_debug_trap' DEBUG
__s=${EPOCHREALTIME%.*}
__u=${EPOCHREALTIME#*.}
__cmd_start_us="${__s}${__u:0:6}"
unset __s __u

# ...

PROMPT_COMMAND="__prompt_command; __timer_arm"

The trap bit is clever and does most of the heavy lifting.

Once I got this working with my PS1 (see below), I asked Claude for any other improvements it could think of. I did this 3 times and incorporated all of its suggestions.

The main things I changed were to lazy-load completions and other imports. This brought the shell startup time down from 600ms to 14ms which I definitely notice.

__load_completions() {
  unset -f __load_completions
  if ! shopt -oq posix; then
    if [ -f /usr/share/bash-completion/bash_completion ]; then
      . /usr/share/bash-completion/bash_completion
    elif [ -f /etc/bash_completion ]; then
      . /etc/bash_completion
    fi
  fi

  # nvm
  [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"

  # uv
  eval "$(command uv generate-shell-completion bash)"
}
complete -D -F __load_completions # trigger on first tab-complete

# ...

# https://github.com/git/git/blob/master/contrib/completion/git-prompt.sh
__git_ps1() { unset -f __git_ps1; . ~/.git-prompt.sh; __git_ps1 "$@"; }

# ...

export NVM_DIR="$HOME/.nvm"
nvm() { unset -f nvm node npm npx; . "$NVM_DIR/nvm.sh"; nvm "$@"; }
node(){ unset -f nvm node npm npx; . "$NVM_DIR/nvm.sh"; node "$@"; }
npm() { unset -f nvm node npm npx; . "$NVM_DIR/nvm.sh"; npm "$@"; }
npx() { unset -f nvm node npm npx; . "$NVM_DIR/nvm.sh"; npx "$@"; }

Then there were some quality-of-life improvements:

HISTCONTROL=ignoreboth:erasedups
shopt -s histappend histverify # append and expand history file
HISTTIMEFORMAT="%F %T " # timestamp entries
HISTSIZE=10000
HISTFILESIZE=20000

# ...

shopt -s globstar  # let '**' match 0 or more files and dirs
shopt -s cdspell   # autocorrect minor typos in cd
shopt -s autocd    # type directory name to cd into it

The biggest of these was using fzf:

__fzf_lazy_init() { unset -f __fzf_lazy_init; eval "$(fzf --bash)"; }
bind '"\C-r": "\C-x1\C-r"'
bind '"\C-t": "\C-x1\C-t"'
bind '"\ec": "\C-x1\ec"'
bind -x '"\C-x1": __fzf_lazy_init'

This is another lazy-loaded bit, but what this gives you is a much better history search (CTRL+R), file search (CTRL+T), and better cd (ALT+C).

Here's what it looks like all together:

__cmd_start_us=0
__cmd_timing_armed=0
__in_prompt=0

__timer_arm() { __cmd_timing_armed=1; }
__timer_debug_trap() {
  [[ $__in_prompt -eq 1 ]] && return 0
  [[ $__cmd_timing_armed -eq 1 ]] || return 0
  __cmd_timing_armed=0
  local s=${EPOCHREALTIME%.*} u=${EPOCHREALTIME#*.}
  __cmd_start_us="${s}${u:0:6}"
}

trap '__timer_debug_trap' DEBUG
__s=${EPOCHREALTIME%.*}
__u=${EPOCHREALTIME#*.}
__cmd_start_us="${__s}${__u:0:6}"
unset __s __u

###

case $- in
    *i*) ;;
      *) return;;
esac

HISTCONTROL=ignoreboth:erasedups
HISTTIMEFORMAT="%F %T "  # timestamp entries
HISTSIZE=10000
HISTFILESIZE=20000
shopt -s histappend histverify # append and expand history file
shopt -s checkwinsize globstar

[ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)"

if [ -z "${debian_chroot:-}" ] && [ -r /etc/debian_chroot ]; then
  debian_chroot=$(cat /etc/debian_chroot)
fi

case "$TERM" in
  xterm-color|*-256color) color_prompt=yes;;
esac

if [ -n "$force_color_prompt" ]; then
  if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then
	  color_prompt=yes
  else
    color_prompt=
  fi
fi

if [ "$color_prompt" = yes ]; then
  PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
else
  PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
fi
unset color_prompt force_color_prompt

# If this is an xterm set the title to user@host:dir
case "$TERM" in
xterm*|rxvt*)
  PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1"
  ;;
*)
  ;;
esac

if [ -x /usr/bin/dircolors ]; then
    test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)"
    alias ls='ls --color=auto'
    alias grep='grep --color=auto'
    alias fgrep='fgrep --color=auto'
    alias egrep='egrep --color=auto'
fi

alias ll='ls -alF'
alias la='ls -A'
alias l='ls -CF'
alias gitroot='cd $(git rev-parse --show-toplevel)'
alias alert='notify-send --urgency=low -i "$([ $? = 0 ] && echo terminal || echo error)" "$(history|tail -n1|sed -e '\''s/^\s*[0-9]\+\s*//;s/[;&|]\s*alert$//'\'')"'

if [ -f ~/.bash_aliases ]; then
    . ~/.bash_aliases
fi

# User defined

shopt -s cdspell   # autocorrect minor typos in cd
shopt -s autocd    # type directory name to cd into it

__load_completions() {
  unset -f __load_completions
  if ! shopt -oq posix; then
    if [ -f /usr/share/bash-completion/bash_completion ]; then
      . /usr/share/bash-completion/bash_completion
    elif [ -f /etc/bash_completion ]; then
      . /etc/bash_completion
    fi
  fi

  # nvm
  [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"

  # uv
  eval "$(command uv generate-shell-completion bash)"
}
complete -D -F __load_completions # trigger on first tab-complete


# Terminal Prompt

# From: https://github.com/metaist/brush
BR_RESET="\e[0;0m"
BR_GREEN="\e[32m"
BR_YELLOW="\e[33m"
BR_BGRED="\e[41m"

# https://github.com/git/git/blob/master/contrib/completion/git-prompt.sh
__git_ps1() { unset -f __git_ps1; . ~/.git-prompt.sh; __git_ps1 "$@"; }

VIRTUAL_ENV_DISABLE_PROMPT=true # we'll handle it ourselves
PROMPT_COMMAND="__prompt_command; __timer_arm"
__prompt_command() {
  local code="$?" # must be first
  local s=${EPOCHREALTIME%.*} u=${EPOCHREALTIME#*.}
  local now_us="${s}${u:0:6}"
  __in_prompt=1

  PS1="\[\e]0;\w\a\]\n" # set terminal title

  local venv=${VIRTUAL_ENV##*/}
  PS1+="${venv:+($venv) }" # venv name

  # add run time of previous command [error code in red]
  local dur_ms=$(( (10#$now_us - 10#$__cmd_start_us) / 1000 ))
  PS1+="${dur_ms}ms"
  if [[ "$code" != "0" ]]; then
    PS1+=" $BR_BGRED[err $code]$BR_RESET"
  fi

  PS1+="\n$BR_GREEN\u@\h$BR_RESET "   # user@host
  PS1+="$BR_YELLOW\w$BR_RESET"        # pwd
  PS1+="$(__git_ps1)"                 # git info
  PS1+="\n\$ "                        # cursor

  __in_prompt=0
}

__prepend_path() { [[ ":$PATH:" != *":$1:"* ]] && PATH="$1:$PATH"; }

# fzf
__fzf_lazy_init() { unset -f __fzf_lazy_init; eval "$(fzf --bash)"; }
bind '"\C-r": "\C-x1\C-r"'
bind '"\C-t": "\C-x1\C-t"'
bind '"\ec": "\C-x1\ec"'
bind -x '"\C-x1": __fzf_lazy_init'

# node/bun
export BUN_INSTALL="$HOME/.bun"
__prepend_path "$BUN_INSTALL/bin"

# node/pnpm
export PNPM_HOME="$HOME/.local/share/pnpm"
__prepend_path "$PNPM_HOME"

# node/nvm
export NVM_DIR="$HOME/.nvm"
nvm() { unset -f nvm node npm npx; . "$NVM_DIR/nvm.sh"; nvm "$@"; }
node(){ unset -f nvm node npm npx; . "$NVM_DIR/nvm.sh"; node "$@"; }
npm() { unset -f nvm node npm npx; . "$NVM_DIR/nvm.sh"; npm "$@"; }
npx() { unset -f nvm node npm npx; . "$NVM_DIR/nvm.sh"; npx "$@"; }

# python
export PYTHONDONTWRITEBYTECODE=1

__prepend_path "$HOME/.local/bin"


📝 Logging Sucks - Your Logs Are Lying To You (Boris Tane). Argues for passing a context object around and logging that object (with all the details you could possibly need) when something goes wrong. Extends the concept of structured logging to "wide events".




📝 Why Stripe’s API Never Breaks: The Genius of Date-Based Versioning (Harsh Shukla). I got through most of this post before it was revealed that Stripe has a version-level description of which features were added to the API and adapters that convert inputs and outputs into the appropriate version level based on date. Very cool, but how do you handle security issues in versions? You options (as far as I can tell are):

  1. Announce you can no longer use a particular version. (Breaks "we support every version".)
  2. Change the behavior of the specific version and re-release with the same version number. (Breaks "this version has this particular behavior".)
  3. Some kind of automatic translation that says "this published version maps to this internal version".

In any case, it's all very nice, but unlikely to impact how most people will design versioned artifacts in the future.




View all posts