Skip to content

Instantly share code, notes, and snippets.

@galligan
Created February 8, 2026 21:31
Show Gist options
  • Select an option

  • Save galligan/0821d9da7115324e9a645b4d30dd39d2 to your computer and use it in GitHub Desktop.

Select an option

Save galligan/0821d9da7115324e9a645b4d30dd39d2 to your computer and use it in GitHub Desktop.

GitButler PR Valet

The Problem Nobody Loves Talking About

GitButler is the best local stack experience that exists. Full stop. Parallel branches, auto-restacking mid-stack edits, absorb, the new CLI — all of it solves a problem that Git itself has ignored for 20 years. I’ve been using it daily and the workflow just clicks in a way that Graphite’s local tooling never quite did.

But here’s the thing: the moment I push a stack to GitHub, I’m on my own.

PRs land on GitHub looking like unrelated branches unless you squint at base refs. When the bottom PR merges, the next one’s diff bloats with already-merged changes (or worse, targets a deleted branch). Mid-stack review feedback means I need to rebase locally, force-push, and hope GitHub’s UI catches up. And if I’ve got an agent working on a fix mid-stack? I’m the router — copying context, rebasing its work, making sure the rest of the stack stays coherent.

The local experience is a 9. The GitHub side is a 4.

That gap is exactly where Graphite carved out its business. Their stack-aware merge queue, the PR comments showing position and blockers, the automatic restacking after merges — those are real quality-of-life features that keep teams from losing an hour a day to stack coordination. But Graphite’s local tooling has always felt like a wrapper around Git’s worst instincts, and it requires buying into their whole ecosystem (accounts, dashboard, their own review UI). For solo devs and small teams, it’s more platform than the problem warrants.

GitButler already has the hard part — the engine. What’s missing is the valet.


What PR Valet Would Do

Valet is a convenience layer that makes GitButler’s stack model team- and agent-friendly by orchestrating what happens on the GitHub side after you push. It doesn’t replace anything GitButler does locally. It picks up where but push leaves off.

Three jobs:

1. Make stacks visible on GitHub. Every PR in a stack gets a single canonical comment — updated in place — showing its position, links to parent and child PRs, check status, and blocking conditions. Anyone landing on PR #3 of 5 immediately knows where they are and what needs to happen before it can merge. This is table-stakes stuff that Graphite gets right and GitHub itself still doesn’t provide.

2. Keep stacks coherent after changes. When the bottom PR merges, Valet retargets the next PR’s base, rebases dependents, force-pushes (with lease), and updates all the stack comments. When someone pushes mid-stack changes, same thing — reconcile the graph, restack, update. One primitive: reconcile. Everything else compiles down to it.

3. Give agents a way in. A remote agent (Claude Code in a container, Codex, whatever) should be able to pick up a stack, understand its shape, make a fix on the right branch, and push — without a human babysitting the coordination. Valet provides the “agent contract”: here’s the stack graph as JSON, here’s which branch you’re working on, here are the rules. Push when you’re done, Valet handles the rest.


The Stack Model

Valet treats stacks as a dependency graph, not a flat list.

  • Node: a PR (or branch, pre-PR)
  • Edge: parent/base relationship
  • Root: the node closest to trunk

This matters because real stacks aren’t always linear. You get offshoots — an agent creates a patch branch off PR #2, fixes something, merges back. You get insertions — realize you need a migration between PR #1 and PR #2. You get folds — PR #3 turns out to belong in PR #2, so you absorb it and close #3 as superseded.

GitButler already handles the branching side of this locally. Valet would handle the PR lifecycle side on GitHub: retargeting bases, closing superseded PRs, reparenting children, and keeping status comments accurate through all of it.


Key Operations

Reconcile — the core primitive. Acquire a lock (one reconcile per stack at a time), discover the current graph from GitHub, validate it (no cycles, consistent bases), rebase dependents if needed, push updated heads, retarget PR bases, update comments. If there’s a conflict, stop, annotate the first conflicting node with instructions, and get out of the way.

Merge Stack — bottom-to-top merge with gating. Check that each PR passes checks and has approvals before merging. After each merge, reconcile the remaining stack. Configurable merge method (squash or merge commit). This is the “merge the whole stack” button that Graphite users love.

Fold / Supersede — absorb PR B into its parent A. Close B with a “superseded by #A” comment. Reparent B’s children to A. Reconcile.

Insert — add a new PR between two existing nodes. Update the base pointers. Reconcile.

Adopt — promote an offshoot (a patch branch hanging off a stack node) into the main stack lineage. Treat as an insert at the appropriate position.

All operations are idempotent and resumable. All produce structured audit logs.


Stack Discovery

Valet needs to figure out which PRs belong to a stack. Three modes:

Base-chain discovery — follow GitHub’s PR base/head relationships. This works well for GitButler-created stacks because GitButler already sets PR bases correctly when you push. Walk up from a given PR (find PRs whose base is this PR’s head branch) and down (follow this PR’s base to trunk). Simple, no metadata required.

Valet metadata — a comment block or label on each PR that explicitly declares stack membership and parent pointers. More durable than base-chain (survives branch renames, manual retargeting), but requires Valet to have written the metadata in the first place.

Hybrid — base-chain as default, Valet metadata as override when present. This is probably the right default. You get zero-config discovery for new stacks, and the metadata layer kicks in as Valet starts managing things.


Stack Status Comments

Each PR gets one comment, updated in place. Identified by an HTML comment marker so Valet can find and replace it.

What it shows:

  • Stack position (#2 of 5)
  • Links to parent and child PRs
  • Check status and approval state for each node
  • Whether the PR needs a restack (base changed since last push)
  • Last reconciliation run status
  • Available commands (/valet reconcile, /valet merge-stack, etc.)

This is arguably the single highest-value feature. It turns a pile of PRs into a legible stack with one glance.


Agent Hydration

This is the part that gets interesting for the agent-first future.

The problem: A remote coding agent — whether it’s Claude Code running in a Cloudflare Container, Codex in the cloud, or any other headless agent — gets pointed at a PR and told “fix this.” But that PR is part of a stack. The agent doesn’t know the stack exists, doesn’t know what’s above or below, and definitely doesn’t know how to push changes without breaking the rest of the stack.

What Valet provides:

A hydrate command that gives the agent everything it needs:

  • stack.json — the full graph with PR links, branch names, base relationships, and policies (merge method, protection rules)
  • A focus branch — an ephemeral branch (e.g., valet/<stack-id>/focus) that points to the PR the agent is working on
  • An agent contract — minimal instructions: “you are working on branch X, which is PR #N in this stack. Push to X when done. Do not modify other branches. Valet will reconcile the stack after your push.”

The agent operates on one branch. Valet handles the coordination. The human reviews the result, not the process.

For more advanced setups — say, a Claude Code instance running in a Cloudflare Container that needs to work across multiple PRs in a stack — Valet could prepare a full workspace clone with the stack’s branches checked out. But that’s a later-stage capability. The focus branch mode covers 90% of agent use cases today.


Safety

Stacks are inherently fragile. One bad force-push and you’ve rewritten history that other people (or agents) are working on. Valet needs to be conservative.

  • Locking: one reconcile per stack at a time. No concurrent restacks.
  • Force-push discipline: always --force-with-lease. If the remote has moved since we fetched, abort.
  • Dry run: every operation supports --dry-run (or --check). Show what would happen without doing it.
  • Abort on conflict: if a rebase hits a conflict, stop immediately. Annotate the PR with what went wrong and what the human should do. Never auto-resolve.
  • Audit trail: every run logs its inputs (PR IDs, SHAs), outputs (updated SHAs, retargeted bases), and decisions. Structured, greppable.
  • GitHub App tokens: prefer short-lived installation tokens over PATs. Minimal permissions: contents:write, pull_requests:write, checks:read.

The default posture: don’t make things worse. If Valet isn’t sure what to do, it stops and tells you why.


Implementation Shape

Valet doesn’t need to be heavy. The core is a CLI and a GitHub Action.

CLI (valet or integrated into but)

  • valet discover <pr> — build and print the stack graph
  • valet comment <pr> — post/update stack status comments
  • valet reconcile <pr> [--dry-run] — retarget + rebase + push + update comments
  • valet merge-stack <pr> [--dry-run] — bottom-to-top gated merge
  • valet fold <pr> — absorb into parent, close, reparent children
  • valet insert <pr> --between <a> <b> — insert node, reconcile
  • valet hydrate <pr> --mode focus — prepare agent environment

The CLI talks to GitHub’s API. It can run locally (developer debugging, manual reconcile) or in CI.

GitHub Action Two triggers:

  • pull_request events (opened, synchronize, closed) → auto-update stack comments
  • issue_comment → parse /valet <command> slash commands and run them

That’s enough for Phase 1. No servers, no infrastructure beyond what GitHub already provides.

Phase 2 could add a lightweight webhook receiver (a single Worker, or even a small server) for faster response times and the locking/state machine. But honestly, GitHub Actions with a concurrency group gets you surprisingly far.

Phase 3 is where the containerized agent story lives. A Cloudflare Container with but CLI and Valet installed, ready to clone a repo, hydrate a stack, run Claude Code against it, push results, and let Valet reconcile. That’s the “agents as first-class stack participants” vision — but it’s built on top of the same primitives.


What This Isn’t

  • Not a replacement for GitButler. Valet doesn’t touch local workflow. It’s the GitHub-side companion.
  • Not a hosted platform. No accounts, no dashboard, no SaaS. CLI + Action. Open source.
  • Not a merge queue. GitHub has merge queues. Graphite has a stack-aware one. Valet just makes sure the stack is coherent before and after merges. If you want queue semantics, use GitHub’s — Valet works alongside it.
  • Not conflict resolution. Valet rebases mechanically. If there’s a semantic conflict, it stops and asks a human. That’s the right default.

Why This Matters Now

The agent angle is what makes this urgent, not just convenient. We’re past the point where one human works on one branch. Right now, today, people are running multiple Claude Code sessions, Codex tasks, and Cursor agents simultaneously — and GitButler’s workspace model is perfectly designed for that. Multiple parallel branches, auto-assignment of file changes, agents scoped to branches.

But the moment those branches become PRs, the coordination falls apart. The human becomes the router. Copy feedback from a review, paste it into an agent prompt, wait for the fix, check the rebase, force push, update the base, check the comments, move to the next one.

Valet automates that entire middle layer. The human decides what to build and what to merge. The agent does the work. Valet keeps the stack coherent in between.


Where I’d Love to Help

I’ve been building tools in exactly this space — agent coordination, GitHub lifecycle management, PR automation. I’ve already spec’d out the reconciliation algorithm and the data model. Happy to build this as an open-source companion to GitButler, or to collaborate more directly if that’s interesting.

The bones are straightforward: stack graph discovery, a reconcile primitive, status comments, and a thin CLI. The hard part isn’t the code — it’s getting the failure modes right. Making sure Valet never makes a stack worse than it found it. I’ve spent a lot of time thinking about that and I think the approach is solid.

Either way, this gap is real, and the people who fill it are going to capture a lot of the value that Graphite currently holds — without requiring a whole platform to get there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment