Skip to content

Troubleshooting: results on one network, none on another

The same command, the same pixi.lock, a different network — and the vulnerability table is empty, or every license is missing. Nothing in the output says why, because from the tool's point of view nothing went wrong: it asked, it did not get an answer, it carried on and wrote a document.

This page is the order to check things in. Every step is a command you can paste.

1. Ask the tool what it sees

$ pixi sbom --doctor --fetch-licenses --vulnerabilities osv
Configuration
  offline    false
  proxy      HTTPS_PROXY=http://user:***@proxy.corp:8080
  no-proxy   NO_PROXY=.corp,localhost
  TLS roots  the platform verifier (the operating system trust store)
  timeout    120s
  cache      /home/u/.cache/rattler/pixi-sbom (exists)

Upstreams
  PyPI index         https://pypi.org/pypi            ok 200, 184 ms
  OSV                https://api.osv.dev              failed: io: invalid peer certificate: UnknownIssuer
  package archives   each package's own download URL  ok 200, 96 ms

1 of 3 upstream(s) could not be reached: OSV.

--doctor needs no lockfile and no workspace. Give it the flags of the run you are diagnosing, so it probes the same upstreams that run would. It exits 1 when anything is unreachable, which makes it usable as a CI pre-flight.

The three lines that answer most questions are proxy, TLS roots and the failure text beside each upstream. pixi sbom --version-details prints the same configuration plus what the binary was built with.

2. The three usual causes

Symptom in --doctor Cause Fix
failed: io: invalid peer certificate: UnknownIssuer A TLS-intercepting appliance re-signs traffic with a CA the tool does not trust Install the CA system-wide, or point --ca-bundle at it (below)
failed: io: Connection refused / a timeout, with proxy none The network needs a proxy and none is configured Set HTTPS_PROXY (and NO_PROXY for internal hosts)
failed: ... with a socks5:// proxy set — Supported; if it still fails, check pixi sbom --version-details says socks-proxy
ok 200 for everything, but the report is still empty Nothing was asked, rather than nothing found See when something comes back empty

Proxies

Every request honours ALL_PROXY, HTTPS_PROXY, HTTP_PROXY and NO_PROXY (either case), including socks5:// and socks5h:// addresses. On Windows, a proxy configured only in the system settings, with no environment variable at all, is used as well.

The configuration block prints the variable in effect with the password replaced, so it is safe to paste into an issue — internal host names are not, so read it first.

A private certificate authority

Trust comes from the operating system's store by default, which is right wherever the appliance's CA is installed system-wide. Where it is not — a container, a CI image, a machine where only Python was ever configured — point the tool at the bundle:

pixi sbom --ca-bundle /etc/ssl/certs/corp-ca.pem --vulnerabilities osv
export PIXI_SBOM_CA_BUNDLE=/etc/ssl/certs/corp-ca.pem     # the same, for a CI job

SSL_CERT_FILE is honoured when neither is given, which is the variable the rest of the Python and conda world already sets in such an image, so a correctly configured container usually needs nothing at all.

The trust anchors are resolved once, before the first request, and the first of these that is set wins:

  1. --ca-bundle <FILE>
  2. PIXI_SBOM_CA_BUNDLE
  3. SSL_CERT_FILE
  4. the platform verifier — the operating system's own trust store, which is the default when none of the three is set, and which is what every earlier release used

--doctor and pixi sbom --version-details both print which one is in play, as the TLS roots line. Nothing is compiled into the binary: there is no vendored root store to go stale.

The file must be PEM; a bundle that is missing or holds no certificate is reported before the first request, naming the file and the flag that gave it, rather than arriving later as a handshake failure:

$ pixi sbom --ca-bundle ./corp.der --fetch-licenses
Error: pixi_sbom::http::ca_bundle

  × no certificate in the CA bundle at ./corp.der
  help: the file given by --ca-bundle must be PEM, with at least one -----BEGIN CERTIFICATE----- block;
        a DER file must be converted first (openssl x509 -inform der -in ca.der -out ca.pem)

A channel that needs credentials

A private channel or index answers 401, usually on --fetch-licenses, which reads each archive from the host the lockfile names. pixi-sbom reads the credentials the conda ecosystem already keeps, in the order rattler consults them:

Source
RATTLER_AUTH_FILE a credentials file named by the environment
~/.rattler/credentials.json where pixi auth login writes
~/.netrc NETRC overrides the path; _netrc on Windows

So a host already set up for pixi needs nothing further:

pixi auth login artifactory.corp --token "$TOKEN"
pixi sbom --fetch-licenses

Entries are matched by exact host first, then *.domain walking up the labels, so *.corp.example covers artifactory.corp.example. A .netrc default entry applies to any host with no better match.

Bearer tokens, basic auth and conda tokens are all used; a conda token goes in the path as /t/<token>/ the way conda does it. Credentials never appear in a log line, a cache key or a produced document.

The platform keyring is not read. pixi can keep credentials there, and pixi-sbom cannot reach them without a platform dependency that would not help a CI runner anyway. Export the entry into a file and point RATTLER_AUTH_FILE at it.

S3 and OAuth credentials are recognised but not used. A host configured with either is requested unauthenticated, with a warning naming the host, rather than failing silently.

401 and 403 are different problems. A 401 means the host wants authentication, and the warning names what this machine had for it, because a rejected credential and a missing one look identical from the status code and want opposite fixes. A 403 means the host refused — usually a proxy or a policy — and credentials are only named when some were actually sent and refused anyway. A 403 from a host you have no credentials for is not a credentials problem, and is not reported as one.

A rate-limited run

The indexes are free public services, and asking for more parallelism than one will tolerate gets 429 Too Many Requests back. A request that is refused that way is retried a few times, waiting longer each time with a little jitter so a batch launched together does not retry together and reproduce the burst. 502, 503 and 504 are treated the same way: a proxy saying an upstream is briefly out.

Attempts and total waiting are both capped — this is a tool that has to finish, not a daemon — so a genuinely rate-limited run ends rather than sleeping indefinitely. Packages it could not ask about are then named in their own line:

No releases to compare against (1): mylib
Could not be checked (37), the index did not answer: numpy, pandas, ...

Those two lines mean opposite things. The first is a fact about the packages: nothing upstream carries them, which is the normal answer for a private or source package. The second is a failure of the run: the packages have an upstream and we did not manage to ask. Reporting them together would make a rate-limited run look like a confident answer with packages quietly missing from it.

If the second line appears, lower the concurrency (PIXI_SBOM_CONCURRENCY) and run again; the cache keeps whatever did answer, so a second run asks only for the rest.

A timeout or a host that does not resolve is not retried. The first has already spent the whole request budget and the second will not resolve any sooner for being asked twice; retrying either would multiply the worst case rather than recover from it.

A blocked host

pixi sbom --doctor on its own probes every fixed upstream and names the ones that did not answer, so start there rather than guessing which flag reaches which host. Every upstream it can reach lists all eight addresses and the option that brings each one in.

Where the host itself is unreachable and no proxy or CA will change that, point the tool at whatever the network does allow:

Variable What it replaces
PIXI_SBOM_PYPI_URL The PyPI JSON API — a devpi or Artifactory mirror
PIXI_SBOM_OSV_URL The OSV API
PIXI_SBOM_ANACONDA_URL The anaconda.org API used by --report outdated; naming one also lets it be asked about channels the lockfile fetched from elsewhere
PIXI_SBOM_PREFIX_INDEX_URL prefix.dev's GraphQL endpoint, which --report outdated uses by default
PIXI_SBOM_SCORECARD_URL The OpenSSF Scorecard API
PIXI_SBOM_KEV_URL The CISA KEV catalog
PIXI_SBOM_MAPPING_URL The conda-to-PyPI name mapping
PIXI_SBOM_CONDA_ARCHIVE_URL Base for conda package archives, replacing each package's own host
PIXI_SBOM_WHEEL_ARCHIVE_URL Base for PyPI wheel archives, replacing each wheel's host and keeping its path

--pypi-mapping-file <FILE> takes the conda → PyPI name mapping from disk instead of downloading it, for a run with no network at all. That mapping is what an air-gapped run of --vulnerabilities osv over conda packages cannot do without.

3. Read the requests

-v turns on the per-request lines; -vv adds the detail that matters when a run cannot be reproduced on another machine:

Level
-v (debug) every request and its outcome, which credentials file was loaded, cache hits and misses
-vv (trace) per-request credential attribution, the hosts credentials exist for, and response headers

Response headers are the thing to reach for when a request fails and the status does not say why. A proxy or a gateway that strips or rewrites Authorization is invisible from a status code and obvious from the headers. Values that carry a credential are masked by length rather than printed, so a log is safe to paste:

TRACE pixi_sbom::http: response headers url="https://artifactory.corp/..."
      headers="server: Artifactory; set-cookie: <32 chars>; x-api-key: <15 chars>; content-length: 11"

Authorization, Proxy-Authorization, Cookie, Set-Cookie, X-JFrog-Art-Api and X-Api-Key are masked everywhere they appear. A conda token changes the URL that is requested, and the original is what gets logged.

RUST_LOG=pixi_sbom::http=debug,pixi_sbom=info pixi sbom --vulnerabilities osv

Every request is logged with its URL, status, size and elapsed time, and every failure with its whole error chain — invalid peer certificate, Connection refused, the proxy's own message — rather than only the outermost "request failed". What the log says has the module targets and three worked recipes.

4. Work from the cache

An unreachable upstream does not have to stop a run:

PIXI_SBOM_OFFLINE=1 pixi sbom --fetch-licenses --vulnerabilities osv

Nothing is requested; every cache is used where it has an answer and every skipped request is logged. Warm the caches on a machine that can reach the network, copy PIXI_SBOM_CACHE_DIR across, and the restricted machine produces the same document.

What such a run could not finish is recorded in the document itself — pixi:incomplete, pixi:incomplete-detail and pixi:stale-cache (see incomplete enrichment) — so an SBOM built on a restricted network does not read as "no known vulnerabilities" months later.

Reporting it

If none of this explains it, open an issue with the output of pixi sbom --version-details, the command, and the same command with -vv. The bug report form asks for exactly those three.