Skip to content

detect-prerelease-pins.sh: any LAN/loopback string in a build file trips the gate, so a project with a private-network default can never pass #334

Description

@dmealing

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:

  1. 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.
  2. For Gradle specifically, only repositories/pluginManagement blocks are package sources; buildConfigField, manifestPlaceholders, resValue and friends are app config.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions