DEV Community

Cover image for My DuckDB Extension PR Sat Red for Eight Weeks. The Bug Wasn't What the CI Said.
AI Explore
AI Explore

Posted on

My DuckDB Extension PR Sat Red for Eight Weeks. The Bug Wasn't What the CI Said.

On May 10 I opened a pull request to add gpudb— a GPU-accelerated DuckDB extension that runs on both NVIDIA CUDA and Apple Silicon Metal— to the DuckDB community-extensions registry. Then it sat. One red check, no maintainer comment, eight weeks of silence. The failing job was called linux_arm64, so of course I spent the first hour convinced I had an ARM problem. I didn't. The CI was lying to me— not maliciously, just structurally. Here's what actually broke, and how a fleet of AI agents took it from red to green on all four platforms in about seventy minutes.

The misdirection: a platform name is not a diagnosis

The only red job was linux_arm64. Every instinct says "cross-compilation, ARM toolchain, some aarch64 quirk." But the actual error, buried in the log, was three words:

make: *** No rule to make target 'set_duckdb_version'
Enter fullscreen mode Exit fullscreen mode

That is not an ARM error. That is not even a build error. It's make telling you there is no Makefile target by that name— because there was no build harness at all. The community-extensions CI expects every extension repo to ship the extension-template's makefiles. Mine didn't. gpudb was a standalone CMake project; I'd wired it into the registry's metadata but never scaffolded the template build layer the CI actually drives.

So why did only arm64 go red? Scheduling. The matrix runs every platform through the same first step, make set_duckdb_version. The arm64 runner happened to reach that step first, failed, and the matrix's fail-fast cancelled its siblings before they could fail identically. A red platform label was pure noise. The real message was three words in. The lesson I keep re-learning: read the error, not the job name.

The fix: make a standalone CMake project speak the CI's language

gpudb isn't built from DuckDB's C++ extension template— it's its own CMake project with CUDA and Metal backends. I didn't want to rewrite it into the template. The clean path is the C-API extension route, which lets any project emit a loadable .duckdb_extension without linking libduckdb at all:

  • Vendor the C API headers (duckdb.h, duckdb_extension.h) into the repo so the build is hermetic.

  • Adopt the c_api_extensions makefiles from extension-ci-tools— that's the harness the CI's make calls actually expect, set_duckdb_version target and all.

  • Build against the stable C_STRUCT ABI: every DuckDB call goes through a function-pointer struct handed to the extension at load time, so the artifact links zero DuckDB symbols and stays ABI-stable across DuckDB point releases.

  • Pin the registry's ref to the exact commit on main where that harness landed, so the CI checks out a tree that actually has the makefiles.

The only real-world example of this path in the wild is capi_quack, DuckDB's own C-API sample. If you're doing the same thing, that's your reference implementation— read it closely, because the docs assume you started from the template and you didn't.

The trick nobody tells you: green CI before a human ever looks

Here's the part that turns an eight-week wait into a same-day fix. The official checks only run once a maintainer approves the workflow on duckdb/community-extensions— first-time contributors don't get automatic runs. So you can push a fix and then wait days just to learn whether it even compiles.

You don't have to. The build workflow is a normal reusable workflow, and you can workflow_dispatch the identical pipeline on your own fork:

gh workflow run "Community Extension Build" \
 --repo /community-extensions \
 --ref main \
 -f extension=gpudb -f duckdb_version=v1.5.2
Enter fullscreen mode Exit fullscreen mode

Seven and a half minutes later I had DuckDB v1.5.2 (ci-tools v1.5-variegata) building and deploying green on linux_amd64, linux_arm64, osx_amd64, and osx_arm64— the exact same matrix the maintainers will run, on my own runner budget, before asking anyone for anything. That run is public: github.com/singhpratech/community-extensions/actions/runs/28753457602. When I finally pinged the maintainers, I handed them a green four-platform receipt instead of "please try again."

The part that's actually about AI

Everything above is DuckDB packaging trivia. The reason it's on this blog is how it got solved. I didn't sit and grind logs for a day. I ran it as an orchestration:

  • One model as the brain, holding the goal and the plan.

  • Parallel agents on disjoint jobs— one doing CI-log forensics on the failed run, another auditing the repo's build metadata against a known-good template, at the same time.

  • A build agent working in an isolated git worktree, scaffolding the C-API harness and dispatching fork CI to confirm green before a single commit touched the PR branch.

A problem that sat untouched for eight weeks went from diagnosis to four-platform green in about seventy minutes— not because the model is smarter than the problem, but because "read the log, audit the tree, build in a sandbox, verify, then push" is a pipeline, and a pipeline can be parallelized and supervised. The interesting unit of AI work in 2026 isn't the single clever answer. It's the harness around it.

Where it stands

PR #1898 is open and green on the fork; it's now waiting on a maintainer to approve the official run. The full "how gpudb saturates GPU memory bandwidth on CUDA and Metal" story— and the INSTALL gpudb FROM community line— is for merge day. This post is the boring, honest middle: the eight weeks of red, and the three words in the log that were the whole answer.

If you're shipping your own DuckDB extension from a non-template project: vendor the C headers, adopt the c_api_extensions makefiles, build against C_STRUCT, dispatch your fork's CI for proof, and read capi_quack when you get stuck. And when one platform goes red— read the error, not the job name.

Top comments (0)