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:
--ca-bundle <FILE>PIXI_SBOM_CA_BUNDLESSL_CERT_FILE- 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:
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.
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:
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.