trelix v3.2.2 to v3.2.5: The Source Tree Was Fine. The Published Package Wasn't.

trelix v3.2.2 to v3.2.5: The Source Tree Was Fine. The Published Package Wasn't.

2026年9月6日1 次浏览来源:Dev.to阅读原文

Run this against the real, published image and watch it fail: Exit code

127.

Not a crash inside trelix-mcp, not a stack trace, not a permissions error — is the shell's own way of saying the binary you asked for does not exist.

And it didn't.

The console script trelix-mcp is supposed to install as part of every trelix package was simply absent from the image, on both the slim tag and the tag, for the entire life of the 3.2.1 release.

Every unit test in the suite was green.

Every line of source that builds trelix-mcp was correct.

The thing a user would actually get from did not have the binary its own flag implies exists.

This article covers four releases — v3.2.2, v3.2.3, v3.2.4, and v3.2.5 — spanning 173 commits and 88 changed files since v3.2.1, which is where the last article in this series left off.

That one was about tests that pass without exercising the code they claim to cover: a standing in for a real embedder, an all-ones attention mask that makes masked and unmasked math identical, a unit test that asserted a bug as its own specification.

This one, on the heels of the mutation-testing push that closed out that arc, is about a different and in some ways more uncomfortable failure mode: tests that pass while exercising the wrong artifact entirely.

A green pytest run against says nothing about whether the wheel on PyPI, the image on GHCR, or the binary on the GitHub Releases page actually does what it claims.

Those are three separate build products, built by three separate pipelines, and none of trelix's 4,353 collected unit tests had ever touched any of them directly. v3.2.2 through v3.2.4 is the story of finding that gap and closing it with an actual gate, not a promise to be more careful next time. v3.2.5 is a short postscript proving the discipline stuck.

The Docker image that shipped without its own server The 127 above wasn't hypothetical or reconstructed after the fact — it's the literal command a human ran by hand against the real published v3.2.1 image, now baked verbatim into 's Docker check with a comment explaining why: "this exact command returned exit 127 on the published 3.2.1 image before trelix-mcp was added to the Dockerfile." Root cause was doubly blocked.

The Dockerfile's builder stage never had a line — it only ever copied and installed core trelix.

And even if someone had added that line, the repo's had a single bare entry, , that would have silently excluded the whole directory from the build context anyway.

Two independent gaps, each individually sufficient to explain the missing binary, both present at once — the kind of thing an ordinary Dockerfile-diff review would not catch, since the second gap lives in a different file entirely.

The fix, across commits and , rewrites to followed by , and adds the missing plus its install into the same invocation as core.

Bundling it costs roughly 84MB on the slim tag by the release's own measurement — trelix-mcp's dependencies are , , and itself, nothing that pulls in torch — but that 84MB figure is reported prose in the changelog, not a number any script in this repo computes or asserts.

The mechanism that makes it plausible (no ML dependency in trelix-mcp's own ) is verifiable; the exact delta isn't.

The second 3.2.2 bug lived one layer up, in the console script itself.

Before the fix, 's never inspected at all — not incorrectly, not partially, not at all.

There was no , no reference to , nothing.

Running from a real shell didn't print usage; it silently launched the actual MCP stdio server and sat waiting for JSON-RPC input on stdin, the exact opposite of every CLI convention a flag implies.

The fix is small enough to quote in full: Both 3.2.2 bugs share the same shape: something was correct in principle and broken in the specific packaging or invocation path a real user takes.

Neither one could have failed a unit test, because no unit test in the suite ever installed a wheel, pulled an image, or ran a console script as a subprocess against .

They were found by a manual production-verification pass — installing and running the actual shipped PyPI packages and Docker images rather than the source tree — and that pass had, by this point, been run by hand twice: once for 3.2.1, once for 3.2.2.

Running it a third time by hand was the thing the rest of this arc exists to stop doing.

The wildcard leak and the response that mattered more than the fix v3.2.3's actual bug is narrow and specific: 's branch, plus three -scoped queries in , built a SQL pattern directly from a caller-supplied path without escaping it. treats and as wildcards inside the pattern value itself, which has nothing to do with SQL injection — every one of these queries was already parameterized with — and everything to do with 's own semantics.

A of matches not just the directory but also, say, , because the underscore in the filter is read as "any single character," not a literal underscore.

Given how common underscores are in real directory and file names (, ), this wasn't an edge case; it was a routine collision waiting for the right two sibling paths to exist in the same repository.

The fix lives in at , whose docstring is worth quoting because it states the distinction precisely: The sibling method had already been doing this correctly for a while, pairing with an explicit clause — so the fix wasn't a novel idiom, it was applying an already-proven pattern to the four call sites that had been missed.

It's also not a total fix: 's own docstring flags a second, separate, still-open instance of the same defect class on the symbol-name prefix match in , explicitly out of scope for this release.

I'd rather state that than let the fix read as more complete than it is.

What makes v3.2.3 matter for this article isn't the bug — it's what came after it.

The team's own reasoning, written into the new test suite's module docstring, is that no existing test, including Click's -style in-process mocking and the MCP SDK's own official in-memory , is structurally capable of seeing a defect at the real process boundary — the exact class that let trelix-mcp's bug ship in 3.2.1.

In-memory transports never cross an actual OS process boundary; they can't see a console script that isn't installed, or a subprocess that hangs instead of exiting.

So spawns trelix-mcp as a genuine child process, located via — the test fails loudly if the binary isn't on , not just importable from — and talks to it over real stdin/stdout JSON-RPC via the MCP SDK's .

A companion test, , builds real wheels for all four published packages, installs each into a fresh venv, and asserts the installed matches the working tree — never editable, never a live PyPI pull, by explicit design.

That test suite only matters if something forces it to run before a release goes out.

In , the job's dependencies now include a new job that downloads the just-built wheel artifacts, installs them with — never editable — and runs the e2e suite against that install before is permitted to execute.

It is a real edge in the workflow's dependency graph, not a comment or a convention.

Two smaller CI extensions round it out: 's Docker job now runs against every built image, and asserts the rendered Helm chart's image tag matches 's .

Both are the kind of cheap check that would have caught a 3.2.1-shaped regression on the next PR instead of the next production audit.

Automating the verification instead of re-typing it v3.2.4's capstone answers a question the last two releases raised without answering: once you've written the checks, who runs them, and when?

Before this release, the answer was a person, watching the GitHub Actions tab for both the Release and Docker Publish workflows to finish, then running by hand and reading its output. replaces that with a workflow that listens for completion events from both and .

Because that event fires twice — once per upstream workflow finishing — the job does a single, non-blocking check via the GitHub CLI () to see whether the other workflow h

分享
Baike.dev

baike.dev helps you discover great languages, frameworks, databases, DevOps and cloud-native tools.

Quick links

About

Contribute

Found a great developer tool? Share it with the community.

Submit a tool
© 2026 baike.dev Developer EncyclopediaUpdated daily · Discover great developer tools