Konflate: a Go tool that diffs your Flux pull requests before they hit the cluster
If you run Flux, you already know the awkward part of reviewing GitOps pull requests: the Git diff usually shows the input, not the thing that will actually hit the cluster.
A one-line HelmRelease bump or a small Kustomization edit can fan out into a lot of rendered Kubernetes changes. Konflate is built for that gap. It takes a pull request, renders the cluster state at the PR’s merge-base and at the PR head, then shows the resource-level diff in a review UI that looks much closer to what an operator actually needs.
It is also a nice Go codebase to study because it stays honest about boundaries. Konflate does not try to be a Git forge, a controller, or a deployment system. It is a read-only review service that sits in front of Flux and tells you what a proposed change is likely to do.
The core idea
The project README describes the flow clearly: konflate lists open pull requests, computes the merge-base against the target branch, renders both sides with flate, and turns that into a structured diff. That matches how the code is laid out.
cmd/konflate/main.go wires together config, the forge provider, the render engine, and the HTTP server. From there, the split is straightforward:
internal/provider/talks to GitHub, GitLab, or Forgejo.internal/engine/turns a PR into a rendered diff.internal/server/exposes the UI, API, websocket updates, queueing, and metrics.internal/diff/converts rendered manifests into review-friendly output.
That separation matters because each package is working at a different level of abstraction. The provider package knows about pull requests. The engine knows about rendered trees. The diff package knows about Kubernetes resources. The server package knows about users waiting for results.
Git without shelling out
One detail I liked here is that konflate does not shell out to git. internal/gitclone/gitclone.go uses go-git and keeps a persistent bare mirror on disk, then materializes just the two trees a PR diff needs.
That design buys it a few things.
First, it avoids repeated full clones. Second, it can fetch the forge’s pull-request head ref directly, which is important for fork-based PRs. Third, it keeps timeout and cancellation under Go’s normal context.Context flow, so a slow fetch does not block the rest of the system forever.
The implementation is more careful than a naive “clone twice and compare” approach too. It fetches the base and head, computes the merge-base, and extracts the merge-base tree plus the PR head tree. That matches the behavior reviewers expect from a pull request diff.
Rendering is delegated, not reinvented
The first draft of this post made konflate sound like it directly orchestrates helm template and kustomize build. The code is more interesting than that.
In internal/engine/engine.go, konflate hands the real render work to flate. The engine builds two render trees, shares a source cache between them, and asks flate to render both sides concurrently. Konflate’s job is the orchestration around that: fetching the right trees, managing deadlines, pairing changes, filtering bad edge cases, and packaging the result for review.
That makes sense given what the Flux docs say. A Flux Kustomization is not just a plain kustomization.yaml; it is a reconciliation pipeline around fetching, building, validating, and applying manifests. A Flux HelmRelease similarly describes controller-driven Helm actions rather than a single template command. Konflate gets closer to Flux behavior by leaning on flate’s rendering model instead of bolting together a few CLI invocations and hoping they line up.
Once the manifests exist, internal/diff/ takes over. render.go builds the review payload, impact.go summarizes blast radius, immutable.go looks for immutable-field problems, and lint.go adds warnings that are specific to Flux behavior, like suspended parents or non-pruning removals.
The server side is small but disciplined
The server package is where the app becomes usable rather than merely correct.
internal/server/server.go runs the main HTTP server and a separate metrics server. hub.go handles websocket fanout for live updates. queue.go is worth reading if you like practical concurrency: it uses bounded workers plus per-PR coalescing, so a burst of webhook events does not trigger duplicate renders for the same pull request.
That is a good example of solving the problem in front of you rather than building a generic job system. Konflate only needs one class of background work, keyed by PR number, with a strong preference for “latest state wins.” The queue reflects exactly that.
I also like that the provider layer stays read-only. The README and internal/provider/provider.go are explicit about this: konflate lists and fetches pull requests, but it does not write comments or statuses back to the forge. That constraint keeps the service easier to reason about and safer to run.
Filtering and persistence
Two smaller pieces are easy to miss but worth calling out.
internal/prfilter/prfilter.go does not inspect changed files directly. It compiles a CEL expression over PR metadata and uses that to decide which pull requests should render. That is a better fit for operators, because they can filter by labels, authors, draft state, target branch, and similar review signals without hard-coding repo-specific rules into Go.
internal/persist/persist.go gives the in-memory PR store durability on disk, and internal/engine/cachegc.go cleans up cache data over time. For a tool that spends its life cloning repos, caching sources, and holding rendered diffs, those operational details matter as much as the UI.
What I would borrow
If you are building internal Go tools around Kubernetes workflows, there are a few ideas here worth stealing.
Keep the orchestration layer thin. Let a dedicated renderer do rendering. Put long-running work behind a small queue with explicit coalescing behavior. Treat Git operations as first-class code instead of shell commands. And be clear about what your service will not do.
Konflate is a good reminder that useful infrastructure software does not need to be huge. It needs to be precise about where it sits in the stack and careful about the edges where things usually go wrong.
Full source is at home-operations/konflate on GitHub.