Fixing Go Code Without Knowing Go: An AI-Assisted Open Source Adventure

    Bobby JohnsonJanuary 26, 202612 min read

    It started, as these things often do, with an error message.

    I use beads—a CLI issue tracker that lives in your git repo—every single day. It's become indispensable for tracking work across coding sessions. But here's the thing: beads is written in Go, and I've never written a line of Go in my life.

    So when my routine update command failed spectacularly, I found myself staring at an error I couldn't begin to debug manually.

    The Error That Started Everything

    bash
    go install github.com/steveyegge/beads/cmd/bd@latest

    A command I'd run dozens of times before. This time:

    typescript
    # github.com/dolthub/gozstd
    stream.go:14:48: undefined: DefaultCompressionLevel
    stream.go:31:59: undefined: CDict
    stream.go:47:20: undefined: Writer
    stream.go:56:22: undefined: NewWriterLevel
    stream.go:101:61: undefined: DDict
    stream.go:110:6: undefined: Reader
    stream.go:117:8: undefined: NewReader

    I didn't know what gozstd was. I didn't know what CDict or DDict meant. But I had Claude Code open, and a hunch that maybe—just maybe—we could figure this out together.

    Following the Breadcrumbs

    The first clue came from checking my Go environment:

    bash
    > go version && go env GOOS GOARCH CGO_ENABLED
    go version go1.25.5 windows/amd64
    windows
    amd64
    0

    That last line—CGO_ENABLED=0—would turn out to be the key to everything.

    Claude explained: "The dolthub/gozstd library is a CGO wrapper around the C zstd compression library. On Windows, CGO is disabled by default unless a C compiler is installed."

    CGO. The bridge between Go and C code. Disabled on my Windows machine because I don't have MinGW or any C compiler installed. Why would I? I'm not a Go developer.

    We filed a bug report, documenting the issue thoroughly. But then came the first twist.

    Plot Twist: The Solution Already Existed

    bash
    > gh release view v0.47.1 --repo steveyegge/beads
    
    asset: beads_0.47.1_windows_amd64.zip
    asset: beads_0.47.1_windows_arm64.zip

    Pre-built Windows binaries. They'd been there all along.

    powershell
    irm https://raw.githubusercontent.com/steveyegge/beads/main/install.ps1 | iex

    Problem solved, right? I could have stopped there. But something nagged at me. Other Windows users would hit this same wall. They'd try go install, get a cryptic error about compression libraries, and maybe give up entirely—never knowing that pre-built binaries existed.

    Could we actually fix the root cause?

    Down the Rabbit Hole

    I asked Claude to clone the repo and investigate. What followed was a masterclass in dependency archaeology.

    bash
    > go mod why github.com/dolthub/gozstd
    
    github.com/steveyegge/beads/internal/storage/dolt
    github.com/dolthub/driver
    github.com/dolthub/dolt/go/cmd/dolt/commands/engine
    github.com/dolthub/dolt/go/cmd/dolt/cli
    github.com/dolthub/dolt/go/store/nbs
    github.com/dolthub/gozstd

    The dependency chain revealed itself:

    The problematic gozstd was six layers deep, buried in Dolt's storage layer. Dolt is a version-controlled database—powerful stuff, but it brings heavy dependencies.

    Here's the thing: beads has multiple storage backends. SQLite is the default. Dolt is optional. But the way the code was structured, even if you never used Dolt, you still pulled in all its dependencies at compile time.

    The Fix Takes Shape

    The solution emerged: build tags. Go's mechanism for conditional compilation. If we could make the Dolt backend only compile when CGO was available, Windows users could build beads with just the SQLite backend.

    Claude refactored the storage factory to use a registration pattern:

    go
    // factory.go - always compiled
    var backendRegistry = make(map[string]BackendFactory)
    
    func RegisterBackend(name string, factory BackendFactory) {
        backendRegistry[name] = factory
    }
    
    func NewWithOptions(ctx context.Context, backend, path string, opts Options) (storage.Storage, error) {
        switch backend {
        case "sqlite", "":
            return sqlite.New(ctx, path)
        default:
            // Check if backend is registered (e.g., dolt with CGO)
            if factory, ok := backendRegistry[backend]; ok {
                return factory(ctx, path, opts)
            }
            if backend == "dolt" {
                return nil, fmt.Errorf("dolt backend requires CGO; use sqlite or install from pre-built binaries")
            }
            return nil, fmt.Errorf("unknown storage backend: %s", backend)
        }
    }

    Then a separate file with a build constraint:

    go
    //go:build cgo
    
    // factory_dolt.go - only compiled when CGO is available
    package factory
    
    import "github.com/steveyegge/beads/internal/storage/dolt"
    
    func init() {
        RegisterBackend("dolt", func(ctx context.Context, path string, opts Options) (storage.Storage, error) {
            return dolt.New(ctx, &dolt.Config{Path: path, ReadOnly: opts.ReadOnly})
        })
    }

    The architecture shift was elegant:

    The First Failure

    But we weren't done yet. The build still failed:

    bash
    > go build ./cmd/bd/...
    # github.com/dolthub/gozstd
    undefined: DefaultCompressionLevel

    The factory was fixed, but something else was still importing Dolt directly. A quick grep revealed the culprit:

    bash
    > grep -r "storage/dolt" .
    cmd/bd/init.go:    "github.com/steveyegge/beads/internal/storage/dolt"

    The init command—the one that sets up new beads repositories—was directly importing and using the Dolt package. It needed to go through the factory instead:

    go
    // Before: Direct import
    import "github.com/steveyegge/beads/internal/storage/dolt"
    
    store, err = dolt.New(ctx, &dolt.Config{Path: storagePath})
    
    // After: Factory pattern
    import "github.com/steveyegge/beads/internal/storage/factory"
    
    store, err = factory.New(ctx, backend, storagePath)

    Victory

    bash
    > set CGO_ENABLED=0 && go build ./cmd/bd/...

    No output. In Unix philosophy, silence means success.

    We verified it worked both ways:

    bash
    > set CGO_ENABLED=0 && go build ./cmd/bd/...  # Windows without C compiler
    > set CGO_ENABLED=1 && go build ./cmd/bd/...  # Systems with CGO

    Both succeeded. The fix was complete.

    The Pull Request

    The PR went up with a clear explanation:

    **Problem:** `go install` fails on Windows due to CGO dependency in dolt backend **Solution:** Use Go build tags to make the dolt backend conditional **Result:** SQLite backend (the default) works without CGO

    Reflection

    I contributed a meaningful fix to a Go project without knowing Go. Not a documentation fix. Not a typo correction. An actual architectural change involving build tags, registration patterns, and conditional compilation.

    How? By treating the AI as a collaborator rather than a oracle. I didn't ask "fix this for me." I said "let's investigate" and "can we fix this?" The pronoun matters. We traced the dependency chain. We tried the first fix and watched it fail. We found the second import and fixed that too.

    The AI brought Go expertise I lack. I brought context about how the tool is actually used, the patience to follow the investigation wherever it led, and the judgment about whether a fix was worth pursuing.

    Some might argue this isn't "real" programming. That I didn't learn Go, I just had an AI write it for me. But I'd counter: I understood the problem deeply. I followed the architecture. I made decisions about approach. The syntax was delegated, but the engineering wasn't.

    And now, the next Windows user who tries go install github.com/steveyegge/beads/cmd/bd@latest will just... have it work.

    That feels like a contribution worth making.

    Update: Merged

    The PR was accepted. Steve Yegge's review:

    "LGTM. Clean build tag approach for optional CGO dependency. Good error message for users who need dolt without CGO."

    Sometimes the best validation isn't just that the code works—it's that someone who does know the language looked at what you produced and said "yeah, that's the right way to do it."


    The bug report and pull request are public if you want to follow the full conversation.