Back to Blog

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

Prateek SinghJuly 5, 20265 min read81 views
My DuckDB Extension PR Sat Red for Eight Weeks. The Bug Wasn't What the CI Said.

A DuckDB community-extension PR sat red for eight weeks over a job named linux_arm64. The real bug was three words in the log — and a fleet of AI agents took it green on all four platforms in about seventy minutes.

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'

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 <you>/community-extensions \
  --ref main \
  -f extension=gpudb -f duckdb_version=v1.5.2

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.

Subscribe to new posts from theaivibe.org

No spam — just new posts. One-click unsubscribe.
Share this article

Related Posts

DataFusion on the Apple Silicon GPU: One Optimizer Rule, the Same SQL, Sorts 6.9x to 28.8x Faster
Data Engineering10 min read

DataFusion on the Apple Silicon GPU: One Optimizer Rule, the Same SQL, Sorts 6.9x to 28.8x Faster

Apache DataFusion runs any physical optimizer rule you register. ArrowMetal 0.4.0 ships one for the Apple silicon GPU: the SQL is unchanged, the answers are DataFusion's, and full sorts of 250,000 to 50 million rows run 6.9x to 28.8x faster on an M4 Max. What it takes, what it leaves, and why.

34 views
Read
sqljev: TypeSafe Jev's jev() for SQL Server, Postgres, Snowflake, BigQuery and DuckDB, on Jev or Open-Weight Laya
Data Engineering10 min read

sqljev: TypeSafe Jev's jev() for SQL Server, Postgres, Snowflake, BigQuery and DuckDB, on Jev or Open-Weight Laya

SQL cannot say 'the customer threatens to cancel'. sqljev adds jev(), jev_prob() and jev_choice() to SQL Server, PostgreSQL, MySQL, Snowflake, Databricks, BigQuery, Redshift and DuckDB, and answers them with Laya, an open-weight decision model that runs on your hardware, returns calibrated probabilities instead of text, and can be fine-tuned on your own tables. 140,000 decisions in 271 seconds on one laptop GPU; re-running all 13 queries, 0.9 seconds. Apache 2.0, version 0.1.0.

99 views
Read
pankhllm: The LLM Gateway That Learns to Skip the LLM, Without Replacing the Stack You Already Run
Data Engineering10 min read

pankhllm: The LLM Gateway That Learns to Skip the LLM, Without Replacing the Stack You Already Run

Most of an agent's LLM calls are not writing anything. They are decisions: which tool, which skill, which parameters, made thousands of times a day by a model paid in seconds and tokens. pankhllm sits where your app already calls an LLM, learns those decisions from its own traffic, and starts making them in 0.2 ms on a CPU with a 262 KB model. What it is not sure about still goes to your LLM. On the same 14 questions: a 12B planner 1,743 ms, Laya 49 ms, pankhllm's own model 4 ms, all 14 correct. Here is what it is, what it is not, and where it stops.

111 views
Read