Found while evaluating 0.24.0-rc.3 in an adopter project.
The detector's pattern #2 flags "ANY private-network or loopback host in a manifest/lockfile". That catches strings which are not package sources at all, and there is no way for a project to pass.
Repro
An Android module with a LAN default for its own backend:
// android/app/build.gradle.kts:21
buildConfigField("String", "SERVER_URL",
"\"${project.findProperty("SERVER_URL") ?: "http://10.0.0.5:8000"}\"")
The detector reports:
✖ project points at a private-network or loopback package registry
./android/app/build.gradle.kts:21: ... "http://10.0.0.5:8000" ...
exit 1
That is an application config default — where the built app looks for its own server. It says nothing about where Gradle resolves dependencies from. The project has never been linked to a private registry, and after a full unlink (verified: zero -rc pins, zero registry hosts in any manifest, .npmrc gone) it still exits 1.
Why it matters
The header calls this check load-bearing and says to run it in CI and pre-commit and to "treat a failure here as a build break, never as advice." A permanent false positive inverts that: the only way to get a green build is to stop running the check, or to train everyone to ignore it — which is exactly how the true positive it exists to catch gets waved through.
It also makes the unlink round-trip unverifiable. unlink runs the detector as its final step, so it reported
✗ unlink left pre-release references behind (listed above) — fix them before pushing
when nothing pre-release remained. The real residue in that run was separate and genuine (see #323 — worktree manifests), but the LAN false positive is indistinguishable from it in the output, so the signal that would have told me unlink was incomplete was buried in noise I had to rule out by hand.
Suggested narrowing
Pattern #2 is trying to answer "does this project resolve PACKAGES from a private host". That is a question about resolution config, not about any string in any build file. Options, roughly in order of preference:
- Scope the host scan to the places a package source can actually be declared —
.npmrc / NuGet.config / pip.conf / [[tool.uv.index]] / pyproject index URLs / <repositories> and <pluginRepositories> in pom.xml / repositories { } blocks in Gradle — rather than whole-file greps of manifests.
- For Gradle specifically, only
repositories/pluginManagement blocks are package sources; buildConfigField, manifestPlaceholders, resValue and friends are app config.
- Failing that, an ignore mechanism (a
.prereleaseignore, or honouring a trailing # not-a-registry), so a project with a legitimate LAN default can run the gate at all.
Patterns #1 (the registry host), #3 (vendor pre-release pins) and #4 (bare dist-tags) were all accurate in this evaluation — #3 in particular correctly caught every repinned manifest. It is only the blanket private-host scan that misfires.
Found while evaluating
0.24.0-rc.3in an adopter project.The detector's pattern #2 flags "ANY private-network or loopback host in a manifest/lockfile". That catches strings which are not package sources at all, and there is no way for a project to pass.
Repro
An Android module with a LAN default for its own backend:
The detector reports:
That is an application config default — where the built app looks for its own server. It says nothing about where Gradle resolves dependencies from. The project has never been linked to a private registry, and after a full
unlink(verified: zero-rcpins, zero registry hosts in any manifest,.npmrcgone) it still exits 1.Why it matters
The header calls this check load-bearing and says to run it in CI and pre-commit and to "treat a failure here as a build break, never as advice." A permanent false positive inverts that: the only way to get a green build is to stop running the check, or to train everyone to ignore it — which is exactly how the true positive it exists to catch gets waved through.
It also makes the
unlinkround-trip unverifiable.unlinkruns the detector as its final step, so it reportedwhen nothing pre-release remained. The real residue in that run was separate and genuine (see #323 — worktree manifests), but the LAN false positive is indistinguishable from it in the output, so the signal that would have told me
unlinkwas incomplete was buried in noise I had to rule out by hand.Suggested narrowing
Pattern #2 is trying to answer "does this project resolve PACKAGES from a private host". That is a question about resolution config, not about any string in any build file. Options, roughly in order of preference:
.npmrc/NuGet.config/pip.conf/[[tool.uv.index]]/pyprojectindex URLs /<repositories>and<pluginRepositories>inpom.xml/repositories { }blocks in Gradle — rather than whole-file greps of manifests.repositories/pluginManagementblocks are package sources;buildConfigField,manifestPlaceholders,resValueand friends are app config..prereleaseignore, or honouring a trailing# not-a-registry), so a project with a legitimate LAN default can run the gate at all.Patterns #1 (the registry host), #3 (vendor pre-release pins) and #4 (bare dist-tags) were all accurate in this evaluation — #3 in particular correctly caught every repinned manifest. It is only the blanket private-host scan that misfires.