vibe-qc tagged v0.4.0 at 14:20 on April 27. By midnight on April 28 it had tagged seven more releases. All eight share the codename “Schrödinger’s Llama.” None of the patches fixed quantum chemistry bugs. Every single one fixed something that only surfaced when a real user on a non-dev machine tried to install and run the code.
This is the story of those nine hours and forty-one minutes, and what they taught about the gap between a working release and a usable one.

v0.4.0: what shipped
The v0.4.0 tag represented the culmination of the development arc described in the previous post: end-to-end periodic SCF across all method and spin combinations, effective-core potentials via libecpint, Fermi-Dirac smearing, Saunders-Hillier level shifting, quadratic SCF fallback, DFT-D3(BJ) dispersion, and 25 tutorials with theory sections and verified citations. 879 tests passing, one xfailed, Sphinx building clean with -W. The GitLab CI pipeline auto-deployed the docs site on push. The release branch was public.
What the pre-flight checks cannot tell you is what happens when someone on a different OS, with a different CMake version, on a different Linux distribution, follows your quickstart from a cold start.
v0.4.1: the build system has opinions (+7h 24m)
The first stress test came from running setup_native_deps.sh on Arch Linux. Three independent failures inside an hour:
Eigen 5 detection. The check in build_libint.sh used ls a b to test for header existence. On Arch, Eigen 5 installs only at /usr/include/eigen3/ with no legacy symlink at /usr/include/Eigen/. The ls call returned non-zero, the check reported Eigen missing, and the build failed — even after a fresh pacman -S eigen. Fix: replace with pkg-config --exists eigen3 plus a [ -e ... ] short-circuit.
CMake 4.x compatibility. libxc 7.0.0, FFTW 3.3.10, and several libecpint vendored dependencies still declare cmake_minimum_required(VERSION <3.5), which CMake 4.2 on Arch rejects outright without an override. Fix: add -DCMAKE_POLICY_VERSION_MINIMUM=3.5 to every vendored-dep CMake invocation.
CMP0167 Boost warning. libint 2.13.1 uses the deprecated find_package(Boost) interface, which produces a wall of CMake dev warnings that frightened users into thinking something was broken. Fix: add -Wno-dev.
The lesson is not specific to these three bugs. It is that build systems accumulate assumptions about the developer’s environment — which headers live where, which CMake version the project owner happens to be running, which deprecated interfaces are still tolerated on the local machine — and those assumptions are invisible until someone runs the script on a different machine.
v0.4.2: “I built the docs” does not mean “I followed them” (+8h 2m)
After getting past the build, I followed my own quickstart on a clean box. Three things blocked me before the first SCF calculation ran.
The landing page pointed users to the pre-orchestrator scripts: ./scripts/build_libint.sh && ./scripts/setup_basis_library.sh. Those two scripts leave libxc, spglib, FFTW, and libecpint unbuilt. The correct entry point is ./scripts/setup_native_deps.sh, which runs everything. The fix was straightforward; the fact that the wrong path survived the docs CI is the point. CI proves the docs are warning-free. Only a real user proves the docs make sense.
The “your first calculation” snippet had no filename, no run command, and no mention that import vibeqc requires the virtualenv’s Python specifically. A reader who installed into a venv and then typed python water.py got ModuleNotFoundError and no obvious next step. The fix: explicit “save as water.py, run with .venv/bin/python water.py,” plus a tip box covering the activate pattern and the failure mode.
There was also no single page that answered “how do I run a vibe-qc calculation?” Added docs/running.md: the virtualenv Python pattern, OMP_NUM_THREADS, tee / nohup / tmux for long jobs, and a common-errors table.
v0.4.3: the update workflow needs to be one command (+8h 11m)
Existing checkouts needed a four-step manual sequence to update: git fetch, checkout the tag, pull, re-run setup_native_deps.sh, pip install, verify. That is too many steps to remember, too many places to make a mistake, and too likely to leave a user on a stale build without realising it.
scripts/update.sh replaced all of it: one command, handles tag/branch/remote refs uniformly, refuses to run on a dirty tree, prints the release banner at the end so the user knows which version they are on. Paired with docs/updating.md covering the common failure modes: banner shows old version (re-install the Python package), missing library (pass --rebuild-native-deps).
v0.4.4 + v0.4.5: two Linux loader bugs, one after the other (+8h 38m, +8h 57m)
These two patches have the same root — the mismatch between how macOS and Linux resolve shared library dependencies — but they hit at different levels of the dependency tree, which is why they needed two separate fixes.

vibeqc_core.so, fixing libxc.so.15. v0.4.5 addressed a deeper issue: DT_RUNPATH (CMake’s default) is non-transitive — when libecpint loads its own deps, the loader does not inherit vibeqc_core.so‘s rpath. Switching to DT_RPATH via -Wl,--disable-new-dtags plus $ORIGIN in libecpint’s own build fixed libcerfcpp.so.3.v0.4.4 fixed ImportError: libxc.so.15: cannot open shared object file. The v0.4 vendoring commit had added libxc, spglib, FFTW, and libecpint under third_party/<dep>/install/lib/ but only listed libint and libecpint in the compiled extension’s rpath. macOS’s DYLD_LIBRARY_PATH fallback search resolved the others silently. Linux’s glibc loader does not have that fallback. Fix: a foreach loop over all five vendored deps adds an rpath entry for each.
v0.4.5 fixed libcerfcpp.so.3 not found even after v0.4.4 — same symptom, different cause. After the extension loaded libecpint successfully (because libecpint was now in the rpath), libecpint itself tried to load its own transitive dependencies (libcerfcpp, pugixml) and failed. The reason: modern CMake emits DT_RUNPATH by default, which is non-transitive. The loader searches it only for the binary’s direct dependencies. When libecpint’s loader runs, it does not inherit the extension’s rpath. Old-style DT_RPATH is transitive. Two-prong fix: -Wl,--disable-new-dtags in cpp/CMakeLists.txt forces DT_RPATH for the extension, and -DCMAKE_INSTALL_RPATH='$ORIGIN' baked into the libecpint build lets it find its own siblings via $ORIGIN.
macOS never revealed either bug because @rpath on macOS is transitive by default and DYLD has the fallback. Every month of development on macOS was silently papering over a problem that any Linux user would hit in the first thirty seconds.
v0.4.6: when you split a change across two commits, write it down (+9h 15m)
The v0.4.5 hotfix was assembled under time pressure: cherry-pick the docs-CI banner fix, ship. What got left behind was the companion commit that adds the RELEASE_CODENAMES catalogue to vibeqc.banner and threads it into the runtime banner string. The result: import vibeqc; print_banner() on v0.4.5 read Release v0.4.5 with no codename. Nine new tests in tests/test_banner_codename.py shipped with v0.4.6 to pin the catalogue contract permanently: PEP 440 dev-suffix stripping, patch-to-minor fallback, three-step lookup. The banner now reads:
Release v0.4.6 "Schrödinger's Llama" — Quantum chemistry for molecules and solids
The fix itself was trivial. The lesson is about process: when a logical change spans two commits and you are mid-crisis, write down which commit goes with which patch. We did not, and a 30-minute omission cost a tagged release.
v0.4.7: the relative-path trap and the Furo TOC (+9h 41m)
Two more user-visible bugs caught by reading the live v0.4.6 docs site cold.
The quickstart said: save water.py, then run .venv/bin/python water.py. A user who ran git clone, then cd ~, then followed the snippet hit .venv/bin/python: No such file or directory. The venv is in the source tree. The quickstart assumed the reader was still in it. The fix: mkdir -p ~/vibeqc-runs/<project> plus explicit absolute paths throughout — ~/path/to/vibeqc/.venv/bin/python water.py. The activate-venv tip uses the same absolute form so it works from any working directory.
Three pages (quickstart, running, updating) were rendering a visible red Furo error block inline: ERROR: Adding a table of contents.... The Furo theme fires this as an in-page assertion when a {contents} directive is present but the right-sidebar already shows the same navigation. Fix: remove the redundant {contents} blocks. The assertion was always technically correct; the sidebar was already doing the job.

Three patterns worth naming
First-deploy bugs are almost entirely environment, not code. The glibc loader’s strictness, DT_RUNPATH vs DT_RPATH semantics, Arch packaging conventions, CMake 4 policy changes — none of these reproduce on the dev machine. They surface within minutes of the first non-dev user trying to install. The only defence is testing on Linux before declaring “ready to ship,” and having a patch-release workflow that is fast enough to respond.
Docs need to be followed, not just built. CI proved the docs were warning-free. It did not prove they worked. v0.4.2 and v0.4.7 are both “I followed my own quickstart and hit a wall” patches. The difference between building docs and following them is the author’s accumulated context — the venv path, the working directory, the sequence of steps that is obvious to someone who wrote the code and invisible to someone who just cloned it.
The patch-release workflow has to be in place before you ship. Seven patches in nine hours is only possible because scripts/update.sh, docs/release_process.md, and the GitLab CI auto-deploy-on-tag pipeline were all documented and operational before v0.4.0 tagged. Without that infrastructure, each patch would have been a half-day affair. The workflow is part of the release.
What comes next

With the “Schrödinger’s Llama” arc closed, the next milestone is v0.5.0 “Wilson’s Otter” — analytic forces, vibrational frequencies, and the periodic gradient machinery that unlocks phonons and elastic constants. The tutorial series grows with it: 25 tutorials at v0.4.7, with the Peierls dimerisation (tutorial 17), NEB reaction paths (tutorial 19), DFT functional comparison (tutorial 15), and the band structure and DOS (tutorial 12) all shipping in the v0.4 window.
The code is at vibe-qc.com, installation instructions in the installation guide, and scripts/update.sh handles everything for existing checkouts. MPL 2.0.


