18 May 2026, 09:00

git close-bubble: Reliably Close Merge Bubbles

git close-bubble demo

I like Merge Bubbles

I like git. I use it for all my programming projects of course, and everything from ~/.ssh/ to ~/.claude/ to my Godot game (coming soon, I promise) to my book formatted in LaTeX, and nearly any directory that’s got custom-edited text files in it.

I generally work alone on my projects, so I don’t need PRs like [github-flow](https://docs.github.com/en/get-started/using-github/github-flow) and especially don’t need the original [git-flow](https://nvie.com/posts/a-successful-git-branching-model/). But I do like to keep groups of commits together. For that, I like merge bubbles.

For example:

*   e3f9cca DONE admin set existing user password
|\
| * 0a6dd90 Admin user_edit: hide set-password panel on self + server-side guard
| * d5dc655 AdminSetPasswordCest: end-to-end round trip on abc
| * ba74df9 placeholder for AdminSetPasswordCest
| * bbb4013 Admin user_edit: set-password form + handler
| * 384f4ba locale: admin user_edit set-password strings (EN + JA)
|/
* 7cd2527 BEGIN admin set existing user password
*   658cc93  DONE admin-driven brand manager registration
|\
| * 6e1f640 register.php: admin-driven success message names the new manager + login URL
| * bcda5fd register.php: gate to admin only post-bootstrap
| * 94b8c63 Admin users list: '+ Add brand manager' link to /login/register.php
|/
* 8eb82ba BEGIN brand manager registration UX

Every set of related commits lives inside a bubble. I start with a BEGIN commit, which generally has either nothing (via --allow-empty) or a minimum change, like updoot the the version of the code.

From there, I stack up a bunch of related commits. When it’s time to close the commit, I just:

  1. Look up the hash of the BEGIN commit
  2. Copy the hash to my paste buffer
  3. git checkout [paste]
  4. git merge --no-ff active-branch -m "DONE with my awesome change"
  5. gitl, which for me means git log --oneline --graph --decorate --all
  6. Look up the newly created hash for the DONE commit
  7. Copy the hash to my paste buffer
  8. git branch -f active-branch [paste]
  9. git checkout active-branch

It’s a lot! It’s a mess; it’s annoying; it’s fragile, but it’s repeatable and I love it.

I asked my AI about it and it was like “yeah no worries mate” (or something like that), and presented me with this script which I have saved in ~/.local/bin/git-close-bubble:

git close-bubble

#!/usr/bin/env bash
# Close a merge bubble started with a "BEGIN ..." commit.
# Usage: git close-bubble <message> [BEGIN-commit]
#        git close-bubble --dry-run [BEGIN-commit]

set -euo pipefail

DRY_RUN=0
if [ "${1:-}" = "--dry-run" ]; then
    DRY_RUN=1
    shift
fi

if [ "$DRY_RUN" -eq 0 ] && [ $# -lt 1 ]; then
    echo "usage: git close-bubble <message> [BEGIN-commit]" >&2
    echo "       git close-bubble --dry-run [BEGIN-commit]" >&2
    exit 1
fi

if [ "$DRY_RUN" -eq 1 ]; then
    MSG=""
    BEGIN_ARG="${1:-}"
else
    MSG="$1"
    BEGIN_ARG="${2:-}"
fi

B=$(git branch --show-current)

if [ -n "$BEGIN_ARG" ]; then
    S=$(git rev-parse --verify "$BEGIN_ARG")
else
    CLOSED=$(git log --merges --format='%P' | awk '{print $1}' | sort -u)
    S=""
    while IFS= read -r c; do
        if ! printf '%s\n' "$CLOSED" | grep -qx "$c"; then
            S="$c"; break
        fi
    done < <(git log --grep='^BEGIN ' --format='%H')
    if [ -z "$S" ]; then
        echo "error: no unclosed BEGIN commit found" >&2
        exit 1
    fi
fi

echo "Would close bubble starting at: $(git log -1 --format='%h %s' "$S")"

if [ "$DRY_RUN" -eq 1 ]; then
    echo "(dry-run; no changes made)"
    exit 0
fi

git checkout "$S"
git merge --no-ff "$B" -m "$MSG"
M=$(git rev-parse HEAD)
git checkout "$B"
git merge "$M"

echo "Done. Branch '$B' now points at $M. Inspect, then 'git push' when satisfied."

It near-magically handles the fragile command line stuff with a single line:

git close-bubble "DONE my cool code"

It does all the 9 steps above without me having to copy hashes and remembering all the incantations.

Dry Run

There is a dry-run option you can use to see what hash it would target as the BEGIN commit.

git close-bubble --dry-run

02 Mar 2026, 17:00

Two Agents, One jQuery Upgrade: A Multi-Agent Workflow in Practice

Two Agents, One jQuery Upgrade: A Multi-Agent Workflow in Practice

Today Claude helped me upgrade AB’s admin system from jQuery 1.12.4 to 3.7.1. Then we were able to remove some Migrate code entirely. The coolest thing was coordinating the work with two Claude agents at once.

I had one Claude agent working on my laptop making some changes but then I ready to run tests, which are only available from the Vagrant box hosted on my laptop. So I started another Claude agent on the Vagrant box. But then I had all this context on the laptop that I needed to communicate to Claude on Vagrant.

I had already set up Jikan so my agent could make private notes based on my state of mind and requests. Hmmmm how about we just use that on the Vagrant box as well?

It worked more easily than I expected. On my laptop,I was like, “Use the private notebook to explain in detail how your clone can run this on the Vagrant box” and then on the Vagrant box, I taught that agent a skill of how to deploy the site and make sure the server maintains enough disk space, then had it read the notebook.

Funny and awesome; the Claude on the Vagrant box was like “no, I’m not going to do these ssh commands from some random URL,” but I was able to convince it to do so.. my first jailbreak? Scary enough, it didn’t take all that much coaxing.

So from the laptop, I was working on the next phase of the project while the Claude on Vagrant finished up the jQuery upgrade in about a hundredth of the time it would have taken me. Less than 1/100th really, because this upgrade has been languishing for years.