Skip to content

Troubleshooting

Field notes from real breakages — each one is already fixed or fenced by the tooling, but the symptoms are documented here so nobody debugs them twice. First reflex for anything odd: qgis-dev doctor (<leader>pv).

Build failures

OGRGeometry* → OGRGeometryH conversion errors in pdal_wrench

Symptom: Debug builds die in external/pdal_wrench with cannot convert 'std::unique_ptr<OGRGeometry>::pointer' … to 'OGRGeometryH' inside PDAL's Geometry.hpp.

Cause: nixpkgs' GDAL propagates $<$<CONFIG:DEBUG>:GDAL_DEBUG> to all Debug consumers; that define switches GDAL's C handles to strict opaque types, which PDAL 2.8.4's headers don't survive. Release builds never define it — upstream CI can't see this.

Fence: all Debug-config profiles pass -UGDAL_DEBUG (it lands after the injected -D, restoring the release-style void* handles; no ABI impact). If you handcraft cmake flags, keep that flag.

Freshly built tools can't load Qt (libQt6Xml.so.6: cannot open …)

Symptom: build-time helpers (crssync) or the built qgis fail with missing shared libraries; readelf -d on the binary shows a RUNPATH with no /nix/store/ entries.

Cause: linking through an unwrapped mold bypasses nix's bintools wrapper — the component that injects store -rpaths (NIX_LDFLAGS) at link time.

Fence: the devshell ships mold-wrapped. If you ever see storeless RUNPATHs again, something reintroduced a bare linker; fix the toolchain, then qgis-dev relink.

crssync: Could not open database …/build/app/share/qgis/resources/srs.db

Symptom: crssync hunts for srs.db under the (uninstalled) install prefix.

Cause: qgisbuildpath.txt is missing from build/output/bin/. QGIS binaries use it to detect "running from a build tree"; it is written at configure time, so deleting build/output by hand removes it and ninja never brings it back.

Fence: never rm -rf build/output bare — use qgis-dev relink, which clears linked artifacts, keeps every compiled object, and reconfigures so configure-time files are regenerated. Additionally, qgis-dev build/run now self-heal: a missing marker is rewritten before launch.

QGIS starts but "Couldn't load SIP module / No module named 'qgis'"

Symptom: the app runs, Python is disabled; the reported Python path starts with …/build/output/share/qgis/python (install layout) instead of …/build/output/python (where the bindings are).

Cause: running-from-build-tree detection failed (missing qgisbuildpath.txt), or something forced install-layout paths — setting QGIS_PREFIX_PATH does exactly that when detection fails.

Fence: qgis-dev run restores the marker automatically, reconfigures if the qgis.PyQt staging is missing, and launches bare (no QGIS_PREFIX_PATH). If you launch the binary by hand, do the same: ./build/output/bin/qgis with no prefix env.

Configure fails: "file failed to create directory … because: File exists"

Symptom: cmake dies creating build/output/python/qgis/<name> and ls -la shows <name> is a regular file where a directory belongs.

Cause: an interrupted/failed parallel build can race the Python staging steps and strand a file copy at its parent directory's path. Later configures then cannot create the directory.

Fence: delete the stray file(s) (rm build/output/python/qgis/<name>) and reconfigure. Content is always regenerable — everything under build/output is derived.

A stale build/ from another toolchain

Mixing objects from an older compiler generation with a new devshell produces confusing failures. If a build tree predates your current flake.lock, retire it (rm -rf build && qgis-dev build) — the ccache keeps the cost honest.

Qt runtime pieces missing (wayland platform, SVG icons, QML modules)

Symptoms: Could not find the Qt platform plugin "wayland" then a GLX abort; blank/missing toolbar icons; or module "QtQuick.Controls" … not found from the welcome screen.

Cause: packaged Qt apps get plugin and QML paths injected by nix's Qt wrapper at install time; a build-tree binary only sees what the devshell exports. Each Qt add-on lives in its own store path (qtwayland, qtsvg, qtimageformats, qtdeclarative).

Fence: the devshell exports QT_PLUGIN_PATH roots for qtbase/qtwayland/qtsvg/qtimageformats and QML_IMPORT_PATH for qtdeclarative. If a new Qt feature complains, add its package to those exports in flake.nix — and never set QT_QPA_PLATFORM_PLUGIN_PATH, which restricts the platform search to a single directory.

Environment & editor

Neovim takes minutes to start in the checkout

lua-language-server roots at the checkout and, unfenced, crawls the whole tree (build/, .git, src/, …) hunting for Lua to preload. The overlay .luarc.json caps preload and ignores the heavy directories. If startup regresses, check that symlink survived (qgis-dev doctor).

No banner / no environment when entering the checkout

Silent cd with no direnv output at all → the direnv hook isn't installed in your shell (direnv status; on NixOS: programs.direnv.enable = true; plus nix-direnv.enable = true;). A direnv: error … .envrc is blocked message → run direnv allow (expected whenever .envrc content changes — that's the trust model working).

Re-evaluation on entry after sidecar changes

use flake path:… keys on the sidecar's content hash, and .envrc watches flake.nix, flake.lock and lib/tasks.sh — so a sidecar commit triggers one re-evaluation on your next entry (seconds when nothing material changed). Profiles, suppressions and the banner are read live and need no reload.

Detected locale "C" … not UTF-8 spam

The devshell exports LANG=C.UTF-8 when the environment lacks one; this warning only survives in terminals opened before that fix loaded. Re-enter the shell.

Qt Multimedia grumbles about pipewire at build time

qt.multimedia.symbolsresolver: Couldn't load pipewire-0.3 during builds is Qt probing for audio it doesn't need there. Harmless.

Git & isolation

git clean -fdx deletes the embed and your build tree

-x removes ignored files — which is exactly what the overlay and qgis-dev-env/ are. Plain git clean -fd is safe.

Something overlay-ish shows in git status

That is an isolation breach — qgis-dev doctor names the file; unstage it (git restore --staged <file>) and re-run bootstrap. The pre-push guard would have blocked the push regardless.