Verification status
Reproduced and root-caused, measured on macOS (Darwin 25.6.0, arm64, Homebrew's unixodbc 2.3.14), while working the framework coverage/mutation improvement plan. Not verified on Linux CI, where the package manager's odbc.pc layout differs and this specific path is not expected to reproduce — the mechanism below is macOS/Homebrew-specific.
Symptom
scripts/coverage.sh fails its check_coverage_roots.sh gate (added for morph#426, to catch a compiler cache serving objects compiled in a different worktree):
check_coverage_roots: 3 of 614 files in the coverage mapping are not
under <this worktree's absolute path>:
/opt/homebrew/Cellar/unixodbc/2.3.14/include/sql.h
/opt/homebrew/Cellar/unixodbc/2.3.14/include/sqlext.h
/opt/homebrew/Cellar/unixodbc/2.3.14/include/sqlucode.h
Ruled out the gate's actual target (a stale compiler cache) before treating this as a false positive: neither ccache nor sccache is installed on this machine (which finds neither), and build/clang-coverage/CMakeCache.txt has no USE_COMPILER_CACHE entry at all — no cache was ever configured for this build.
Root cause
pkg-config --cflags odbc on this machine's Homebrew install emits -I/opt/homebrew/Cellar/unixodbc/2.3.14/include — a plain -I, not -isystem, because Homebrew's odbc.pc sets includedir to the versioned Cellar path rather than the /opt/homebrew/include symlink, and that Cellar path is not part of Xcode clang's implicit system search list (confirmed with clang++ -E -v -). The Lightweight dependency's CMake picks this up via pkg_check_modules/find_package(ODBC) without marking it SYSTEM, so sql.h/sqlext.h/sqlucode.h get real (non--isystem) coverage instrumentation and land in the unfiltered llvm-cov mapping that check_coverage_roots.sh checks — outside the checkout root, tripping the gate.
This is harmless to the actual uploaded report, confirmed by reading scripts/coverage.sh in full: its real report is scoped by the SOURCES=(include/morph examples/...) array (from line ~156), applied to llvm-cov show/report/export, and none of the 3 ODBC paths match any SOURCES entry — they would be silently absent from coverage.lcov regardless of whether this gate passes or fails. The gate is failing on a file that was never going to appear in the report either way.
What would change the verdict
Someone reproducing check_coverage_roots.sh's gate tripping on a header that does affect the actual filtered report (e.g. a SOURCES-matched path outside the checkout) would confirm the gate is still doing its real job correctly elsewhere — this report is only about the specific case of a non-SOURCES-matched foreign path.
Suggested fixes (not attempted here — needs a source change, out of scope for the task that found this)
- Mark the ODBC include path
SYSTEM in the CMake that consumes it (wherever Lightweight's pkg_check_modules/find_package(ODBC) result is added to a target — a -isystem include is excluded from coverage instrumentation entirely, which also matches how every other genuinely-system header in this build is already treated), or
- Teach
check_coverage_roots.sh to distinguish "foreign worktree path" (the real morph#426 threat model — a stale cache serving objects built in a different checkout) from "real vendor header with no SOURCES match" (provably harmless to the report, as shown above) — e.g. by cross-checking each out-of-root path against the SOURCES array before failing.
Option 1 seems preferable: it fixes the root cause (the header genuinely shouldn't be instrumented for coverage at all, matching every other system dependency) rather than special-casing the gate around a symptom.
Verification status
Reproduced and root-caused, measured on macOS (Darwin 25.6.0, arm64, Homebrew's
unixodbc2.3.14), while working the framework coverage/mutation improvement plan. Not verified on Linux CI, where the package manager'sodbc.pclayout differs and this specific path is not expected to reproduce — the mechanism below is macOS/Homebrew-specific.Symptom
scripts/coverage.shfails itscheck_coverage_roots.shgate (added for morph#426, to catch a compiler cache serving objects compiled in a different worktree):Ruled out the gate's actual target (a stale compiler cache) before treating this as a false positive: neither
ccachenorsccacheis installed on this machine (whichfinds neither), andbuild/clang-coverage/CMakeCache.txthas noUSE_COMPILER_CACHEentry at all — no cache was ever configured for this build.Root cause
pkg-config --cflags odbcon this machine's Homebrew install emits-I/opt/homebrew/Cellar/unixodbc/2.3.14/include— a plain-I, not-isystem, because Homebrew'sodbc.pcsetsincludedirto the versioned Cellar path rather than the/opt/homebrew/includesymlink, and that Cellar path is not part of Xcode clang's implicit system search list (confirmed withclang++ -E -v -). TheLightweightdependency's CMake picks this up viapkg_check_modules/find_package(ODBC)without marking itSYSTEM, sosql.h/sqlext.h/sqlucode.hget real (non--isystem) coverage instrumentation and land in the unfiltered llvm-cov mapping thatcheck_coverage_roots.shchecks — outside the checkout root, tripping the gate.This is harmless to the actual uploaded report, confirmed by reading
scripts/coverage.shin full: its real report is scoped by theSOURCES=(include/morph examples/...)array (from line ~156), applied tollvm-cov show/report/export, and none of the 3 ODBC paths match anySOURCESentry — they would be silently absent fromcoverage.lcovregardless of whether this gate passes or fails. The gate is failing on a file that was never going to appear in the report either way.What would change the verdict
Someone reproducing
check_coverage_roots.sh's gate tripping on a header that does affect the actual filtered report (e.g. aSOURCES-matched path outside the checkout) would confirm the gate is still doing its real job correctly elsewhere — this report is only about the specific case of a non-SOURCES-matched foreign path.Suggested fixes (not attempted here — needs a source change, out of scope for the task that found this)
SYSTEMin the CMake that consumes it (whereverLightweight'spkg_check_modules/find_package(ODBC)result is added to a target — a-isysteminclude is excluded from coverage instrumentation entirely, which also matches how every other genuinely-system header in this build is already treated), orcheck_coverage_roots.shto distinguish "foreign worktree path" (the real morph#426 threat model — a stale cache serving objects built in a different checkout) from "real vendor header with noSOURCESmatch" (provably harmless to the report, as shown above) — e.g. by cross-checking each out-of-root path against theSOURCESarray before failing.Option 1 seems preferable: it fixes the root cause (the header genuinely shouldn't be instrumented for coverage at all, matching every other system dependency) rather than special-casing the gate around a symptom.