The libcapnp-dev package on Debian and Ubuntu has a packaging bug in its cmake config files: the imported targets CapnProto::capnp_tool and CapnProto::capnpc_cpp have wrong IMPORTED_LOCATION values, pointing to /usr/lib/bin/capnp and /usr/lib/bin/capnpc-c++ respectively. Those paths do not exist; the actual executables are /usr/bin/capnp and /usr/bin/capnpc-c++. Any cmake project that reads IMPORTED_LOCATION from these targets — for example via $<TARGET_FILE:CapnProto::capnp_tool> — gets a path to a non-existent binary and fails at build or configure time.
The root cause is a mismatch between where Debian correctly installs the cmake config and what CapnProtoTargets.cmake assumes about its own location. Debian's multiarch packaging policy requires architecture-specific -dev packages to install into the arch-specific path (/usr/lib/x86_64-linux-gnu/) so that amd64 and i386 packages can be co-installed without conflict — libcapnp-dev is Multi-Arch: same and complies with this. The cmake config is therefore correctly installed at:
/usr/lib/x86_64-linux-gnu/cmake/CapnProto/CapnProtoTargets.cmake
This is one directory level deeper than what CapnProtoTargets.cmake was written to handle. The file computes _IMPORT_PREFIX by calling get_filename_component(... PATH) four times starting from the cmake file's path, which assumes a non-multiarch install. On the multiarch path, four PATH calls land at /usr/lib instead of /usr:
/usr/lib/x86_64-linux-gnu/cmake/CapnProto/CapnProtoTargets.cmake (file)
/usr/lib/x86_64-linux-gnu/cmake/CapnProto (call 1: file → dir)
/usr/lib/x86_64-linux-gnu/cmake (call 2)
/usr/lib/x86_64-linux-gnu (call 3)
/usr/lib (call 4: wrong prefix!)
The correct result would be /usr (requiring a 5th PATH call). The cmake code was written assuming a non-multiarch install path where 4 calls suffice:
/usr/lib/cmake/CapnProto/CapnProtoTargets.cmake (file)
/usr/lib/cmake/CapnProto (call 1)
/usr/lib/cmake (call 2)
/usr/lib (call 3)
/usr (call 4: correct prefix)
The relevant section of CapnProtoTargets.cmake (a handwritten file, not cmake-generated — its header notes "This file IS NOT USED by the CMake build! The CMake build generates its own version of this script"):
get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_FILE}" PATH)
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
add_executable(CapnProto::capnp_tool IMPORTED)
set_target_properties(CapnProto::capnp_tool PROPERTIES
IMPORTED_LOCATION "${_IMPORT_PREFIX}/bin/capnp${CMAKE_EXECUTABLE_SUFFIX}"
)
add_executable(CapnProto::capnpc_cpp IMPORTED)
set_target_properties(CapnProto::capnpc_cpp PROPERTIES
IMPORTED_LOCATION "${_IMPORT_PREFIX}/bin/capnpc-c++${CMAKE_EXECUTABLE_SUFFIX}"
)
Because _IMPORT_PREFIX resolves to /usr/lib instead of /usr, the IMPORTED_LOCATION for CapnProto::capnp_tool is set to /usr/lib/bin/capnp, which does not exist. The actual capnp executables are at /usr/bin/capnp and /usr/bin/capnpc-c++.
Affected distros and versions
All currently maintained Debian and Ubuntu releases install the cmake config at the multiarch path and exhibit the same wrong _IMPORT_PREFIX. The bug is not version-specific — it affects every packaged version because all install cmake config files to the multiarch path without adjusting the _IMPORT_PREFIX calculation. The capnproto version number is a red herring; the same 4-PATH-call computation exists in all upstream releases.
| Distro | capnproto version | cmake config path |
|---|---|---|
| Debian Bookworm (12) | 0.9.2-2 | /usr/lib/x86_64-linux-gnu/cmake/ |
| Debian Trixie (13) | 1.1.0-2 | /usr/lib/x86_64-linux-gnu/cmake/ |
| Debian Sid (unstable) | 1.4.0-3 | /usr/lib/x86_64-linux-gnu/cmake/ |
| Ubuntu Noble (24.04 LTS) | 1.0.1-4 | /usr/lib/x86_64-linux-gnu/cmake/ |
| Ubuntu Resolute (26.04 LTS) | 1.1.0-2.1 | /usr/lib/x86_64-linux-gnu/cmake/ |
Non-Debian distros (Arch Linux, Alpine, etc.) are not affected — they install cmake config at /usr/lib/cmake/CapnProto/ (the standard non-multiarch path), and the 4-PATH-call computation works correctly there.
Package links:
- Debian package tracker: https://tracker.debian.org/pkg/capnproto
- Debian Bookworm file list: https://packages.debian.org/bookworm/amd64/libcapnp-dev/filelist
- Debian Trixie file list: https://packages.debian.org/trixie/amd64/libcapnp-dev/filelist
- Debian Sid file list: https://packages.debian.org/sid/amd64/libcapnp-dev/filelist
- Ubuntu Noble file list: https://packages.ubuntu.com/noble/amd64/libcapnp-dev/filelist
- Ubuntu Resolute file list: https://packages.ubuntu.com/resolute/amd64/libcapnp-dev/filelist
- Ubuntu Launchpad changelog: https://launchpad.net/ubuntu/+source/capnproto/+changelog
Regression history
The same /usr/lib/bin/capnp error was first reported as Debian bug #920230 (https://bugs.debian.org/920230) in January 2019 when capnproto 0.7.0 was packaged. Dependent packages (notably Mir) broke because the cmake config was pointing executables to the wrong place.
A fix landed in capnproto 0.7.0-2.1 (Debian NMU by Andreas Tille, January 30, 2019) and 0.7.0-1ubuntu1 (Ubuntu, January 25, 2019, by Dimitri John Ledkov). The changelog describes it as: "Workaround generating invalid CapnProtoConfig which is not multiarch cmake aware, and thus yields invalid bin and include paths." The Ubuntu diff is at: http://launchpadlibrarian.net/408213615/capnproto_0.7.0-1_0.7.0-1ubuntu1.diff.gz
This was a packaging-level patch, not an upstream capnproto fix. It patched cmake/CapnProtoConfig.cmake.in to replace the generator expressions and package-relative variable with hardcoded absolute paths:
-set(CAPNP_EXECUTABLE $<TARGET_FILE:CapnProto::capnp_tool>
+set(CAPNP_EXECUTABLE /usr/bin/capnp
CACHE FILEPATH "Location of capnp executable")
-set(CAPNPC_CXX_EXECUTABLE $<TARGET_FILE:CapnProto::capnpc_cpp>
+set(CAPNPC_CXX_EXECUTABLE /usr/bin/capnpc-c++
CACHE FILEPATH "Location of capnpc-c++ executable")
-set(CAPNP_INCLUDE_DIRECTORY "@PACKAGE_CMAKE_INSTALL_FULL_INCLUDEDIR@")
+set(CAPNP_INCLUDE_DIRECTORY /usr/include)
This bypassed the broken _IMPORT_PREFIX / IMPORTED_LOCATION machinery by setting the CAPNP_EXECUTABLE cmake variable to a hardcoded correct path. It did NOT fix CapnProtoTargets.cmake's _IMPORT_PREFIX calculation, which continued to create imported targets with wrong IMPORTED_LOCATION values.
This partial fix has been carried forward into all subsequent Debian/Ubuntu packaging. The current CapnProtoConfig.cmake (verified in Trixie 1.1.0-2) still has the hardcoded executable paths from the 2019 fix:
set(CAPNP_EXECUTABLE /usr/bin/capnp
CACHE FILEPATH "Location of capnp executable")
set(CAPNPC_CXX_EXECUTABLE /usr/bin/capnpc-c++
CACHE FILEPATH "Location of capnpc-c++ executable")
set(CAPNP_INCLUDE_DIRECTORY /usr/include)
And CapnProtoTargets.cmake still has the uncorrected _IMPORT_PREFIX computation (shown in The Bug section above).
The result is that the bug was never fully fixed — only one of its two observable effects was patched. Code that reads ${CAPNP_EXECUTABLE} (the cmake variable) works correctly. Code that uses $<TARGET_FILE:CapnProto::capnp_tool> (the cmake imported target) has always been broken on any Debian/Ubuntu system, from 0.7.0 through 1.4.0 and beyond. The bug was unnoticed because most consumers used ${CAPNP_EXECUTABLE} or constructed tool paths themselves; it only surfaced when bca7f2aa switched to using $<TARGET_FILE:CapnProto::capnp_tool>.
Verification
To observe the bug on any system with Docker:
docker run --rm ubuntu:24.04 bash -c '
apt-get update -q && apt-get install -y -q libcapnp-dev 2>/dev/null
echo "=== _IMPORT_PREFIX calculation (4 PATH calls from file) ==="
grep -A5 "_IMPORT_PREFIX" /usr/lib/x86_64-linux-gnu/cmake/CapnProto/CapnProtoTargets.cmake
echo "=== capnp_tool IMPORTED_LOCATION ==="
grep "capnp_tool" /usr/lib/x86_64-linux-gnu/cmake/CapnProto/CapnProtoTargets.cmake | grep IMPORTED_LOCATION
echo "=== Wrong path (should not exist) ==="
ls /usr/lib/bin/capnp 2>&1
echo "=== Correct path (should exist) ==="
ls /usr/bin/capnp
'
To extract and examine the package without Docker, on any system with dpkg-deb (or nix-shell -p dpkg):
# Download any affected version, e.g. from Debian's pool:
curl -fL "https://ftp.debian.org/debian/pool/main/c/capnproto/libcapnp-dev_1.1.0-2_amd64.deb" -o libcapnp-dev.deb
mkdir extract && dpkg-deb -x libcapnp-dev.deb extract/
grep -A5 "_IMPORT_PREFIX" extract/usr/lib/x86_64-linux-gnu/cmake/CapnProto/CapnProtoTargets.cmake
grep "IMPORTED_LOCATION" extract/usr/lib/x86_64-linux-gnu/cmake/CapnProto/CapnProtoTargets.cmake | grep capnp_tool
# Shows: IMPORTED_LOCATION "/usr/lib/bin/capnp" (wrong; /usr/bin/capnp is correct)
Upstream status and possible fix
No upstream fix has been committed. The closest upstream discussion is capnproto issue #1802 (https://github.com/capnproto/capnproto/issues/1802), about CMake failing in cross-compilation contexts — related but about a different failure mode (running the wrong architecture's binary rather than a path that doesn't exist).
The root cause from the upstream perspective: CapnProtoTargets.cmake is a handwritten compatibility file whose _IMPORT_PREFIX calculation hard-codes the assumption that it is installed exactly 3 directory levels below the prefix (i.e., <prefix>/lib/cmake/CapnProto/). The Debian multiarch path adds one more level (<prefix>/lib/x86_64-linux-gnu/cmake/CapnProto/), breaking the assumption.
Where an upstream fix would go: The handwritten CapnProtoTargets.cmake should be replaced with a properly generated package config using configure_package_config_file() from CMakePackageConfigHelpers, which dynamically computes the relative path from the cmake file's install location back to the prefix and handles arbitrary install destinations correctly: https://cmake.org/cmake/help/latest/module/CMakePackageConfigHelpers.html
As a distro-level fix (without changing upstream), the packaging could:
- Add one more
get_filename_component PATHcall inCapnProtoTargets.cmake(line 203, after the existing four). This is the minimal targeted fix:_IMPORT_PREFIXwould compute to/usrandIMPORTED_LOCATIONfor both tool targets would be correct. - Move cmake config files to the non-multiarch path (
/usr/lib/cmake/CapnProto/) so the existing four PATH calls compute the correct/usrprefix. However this would conflict with Debian multiarch policy:libcapnp-devisMulti-Arch: same, so the amd64 and i386 packages must install to non-overlapping paths. Moving cmake config to a shared non-arch path would cause a file conflict on co-installed architectures. - Extend the existing partial fix: also hardcode
set_target_properties(CapnProto::capnp_tool PROPERTIES IMPORTED_LOCATION /usr/bin/capnp)etc. inCapnProtoConfig.cmakeafter theinclude(CapnProtoTargets.cmake)line. TheCAPNP_EXECUTABLEvariable is already hardcoded (the 2019 fix); this would bring the imported target'sIMPORTED_LOCATIONinto agreement with it. Fragile for the same reason as the original fix (assumes/usr), but consistent with the existing approach and minimal in scope.
How this affected Bitcoin Core / libmultiprocess
Currently Bitcoin Core and libmultiprocess locate the capnp tools by constructing paths from a capnp_PREFIX macro (the capnproto install prefix, /usr on Debian/Ubuntu). This approach never reads IMPORTED_LOCATION and is unaffected by the packaging bug.
The bug surfaces in the context of an in-progress effort to port Bitcoin Core's IPC feature to Windows/MSVC. That port requires switching from prefix-derived tool paths to cmake-provided target references, since on Windows executables are under tools/capnproto/ rather than bin/ and prefix-derived paths don't generalize. The changes below are proposed commits under active development in [libmultiprocess PR #317](https://github.com/bitcoin-core/libmultiprocess/pull/317); none have been merged yet.
bca7f2aa (April 21, 2026) — "ipc: Replace capnp_PREFIX path construction with cmake-provided symbols" switched mpgen to use $<TARGET_FILE:CapnProto::capnp_tool> and $<TARGET_FILE:CapnProto::capnpc_cpp>, which resolve to each target's IMPORTED_LOCATION at cmake generation time. This fixed the MSVC/Windows build correctly. On Debian/Ubuntu, however, IMPORTED_LOCATION is the broken /usr/lib/bin/capnp (the cmake variable ${CAPNP_EXECUTABLE} would have given the correct path, but $<TARGET_FILE:...> reads IMPORTED_LOCATION directly and bypasses it), so the commit broke the ASan/UBSan CI job — see the next section.
The subsequent commits are proposed workarounds that preserve the Windows fix while handling the broken IMPORTED_LOCATION on Debian/Ubuntu:
7cb83a5d (April 22, 2026) — validates each tool target's IMPORTED_LOCATION on disk and, if the path doesn't exist, runs find_program() to locate the binary and updates the imported target in place.
049dae17 (July 21, 2026) — does an EXISTS check on IMPORTED_LOCATION in compat_config.cmake: if the path is valid it's passed to mpgen as a compile definition; if not, the definition is omitted and gen.cpp falls back to the capnp_PREFIX-derived path. On Debian/Ubuntu the fallback gives /usr/bin/capnp; on Windows/vcpkg the cmake-provided path is valid and used directly.
a26a08496b8 (August 1, 2026) — supersedes the above: passes the CAPNP_EXECUTABLE and CAPNPC_CXX_EXECUTABLE cmake variables set by CapnProtoConfig.cmake as compile definitions to mpgen, and uses them in gen.cpp instead of constructing paths from capnp_PREFIX. These variables are correctly set on all platforms without any EXISTS check or find_program() fallback. On Debian/Ubuntu, the 2019 packaging fix already hardcoded them to /usr/bin/capnp and /usr/bin/capnpc-c++, bypassing the broken IMPORTED_LOCATION. On vcpkg/Windows they hold generator expressions ($<TARGET_FILE:CapnProto::capnp_tool>) that target_compile_definitions evaluates at generation time; vcpkg's vcpkg_cmake_config_fixup ensures IMPORTED_LOCATION for tool targets is updated to tools/capnproto/ before those expressions are evaluated. On other platforms (Arch, Nix, etc.) the same generator expression approach works because IMPORTED_LOCATION is correct to begin with.
"ASan + LSan + UBSan + integer" CI failure
With bca7f2aa applied and without any of the later workarounds, Bitcoin Core's "ASan + LSan + UBSan + integer" CI job fails because cmake cannot locate the capnp executables. An example failure is job 72397341891, on branch 13de58e1 which contains bca7f2aa without any of the subsequent workaround commits. This is the only job affected because it is the only one that combines all three conditions:
Debian/Ubuntu system with apt-installed capnproto: The ASan job uses
CI_IMAGE_NAME_TAG=mirror.gcr.io/ubuntu:24.04(set since December 2023, PR #28992), setsNO_DEPENDS=1, and installslibcapnp-dev capnprotofrom apt. This means it uses the system cmake config with the brokenIMPORTED_LOCATION.IPC enabled: The ASan job's
BITCOIN_CONFIGdoesn't disable IPC, and as of 3cbf747c328 (June 2025)ENABLE_IPCdefaults toON. This triggers the cmake code that readsCapnProto::capnp_tool'sIMPORTED_LOCATION.Not insulated by
dependsorBUILD_FOR_FUZZING: Other jobs that might also be on Debian/Ubuntu and install apt capnproto are either protected bydepends(which builds capnproto from source, bypassing the system cmake config entirely) or byBUILD_FOR_FUZZING=ON(which at the time explicitly setENABLE_IPC OFF).
Specifically, why other Ubuntu 24.04 jobs were not affected at the time:
fuzz job: Also on Ubuntu 24.04,
NO_DEPENDS=1, installslibcapnp-dev, but set-DBUILD_FOR_FUZZING=ON. At the time that option explicitly setENABLE_IPC OFF, so the capnproto cmake targets were never consulted. IPC has since been enabled in fuzz builds, so the fuzz job would now also fail with bca7f2aa and without a workaround.tsan job: Ubuntu 24.04 but uses
DEP_OPTS(the bitcoin depends build system) to compile capnproto from source. No systemlibcapnp-devused.msan job: Ubuntu 24.04 but also uses
DEP_OPTS/ depends. Same reason.native_valgrind: At the time was on Debian Trixie, which also has the broken cmake config. However, the valgrind job simply wasn't part of this CI run — it doesn't appear in the job list for that run at all. Had it run, it likely would have failed too.
Current state (August 2026)
The workaround in a26a08496b8 should be sufficient. The bug has not been fixed in any Debian/Ubuntu release as of August 2026, and the workaround is robust: it routes around IMPORTED_LOCATION entirely by using cmake variables that CapnProtoConfig.cmake sets correctly on all platforms regardless of whether the underlying packaging bug is ever fixed.
Today, more CI jobs are exposed to the bug than in April 2026: the fuzz job now has IPC enabled (it didn't then), and several jobs have migrated from Debian Trixie or Ubuntu 24.04 to Ubuntu 26.04 while continuing to install libcapnp-dev from apt with NO_DEPENDS=1. The cmake variable workaround handles all such cases transparently.
An upstream fix to the Debian/Ubuntu packaging (either via configure_package_config_file() upstream, or a distro patch adding an extra get_filename_component PATH call to CapnProtoTargets.cmake) would be welcome for the benefit of other projects that read IMPORTED_LOCATION directly. For libmultiprocess itself, the current workaround does not depend on such a fix and will continue to work correctly whether or not the packaging bug is resolved.