Skip to content
heliaPROFILER
User guide
HELIA

Field diagnostics

hpx doctor --bundle lets a customer or field engineer hand a maintainer one sanitised archive instead of a screen share. This page is the design behind it, for anyone extending the collectors or auditing what leaves the building; how to produce one is on Troubleshooting.

The support bundleThe four phases of collect_support_bundle() and write_support_bundle(). Every collector degrades to an unavailable section with a reason rather than failing the bundle.
  • Collectorsone section each
    • checks
    • compatibility
    • dependencies
    • modules
    • config
    • probes
    • ports
    • host
  • Redactiondiagnostics/redact.py
    • paths
    • URLs
    • token shapes
    • secret assignments
    • serial numbers
  • Manifesthpx.support-bundle-manifest v1
    • sections
    • artifacts
    • redaction counts
  • Archive
    • deterministic zip
    • content fingerprint
    • verify
Text description of the diagram
  • Collectors (one section each): checks, compatibility, dependencies, modules, config, probes, ports, host
  • Redaction (diagnostics/redact.py): paths, URLs, token shapes, secret assignments, serial numbers
  • Manifest (hpx.support-bundle-manifest v1): sections, artifacts, redaction counts
  • Archive: deterministic zip, content fingerprint, verify

collect_support_bundle(options) runs one collector per section. None of them can fail the bundle: a missing --workspace, an unresolvable --config, absent hardware or no network each mark only their own section unavailable, with a reason, and the rest is still collected (SupportBundleSection.available in results/support_bundle.py).

Section Source Present
checks hostenv.doctor.inspect_environment(include_versions=True) always
compatibility deps.compatibility.load_compatibility_baseline() always, offline
dependencies and nsx.lock deps.dependencies.read_dependency_lock_provenance() plus a redacted copy of the lock text with --workspace
modules the baseline’s qualified modules, plus the resolved modules from the same provenance read always; the resolved half with --workspace
config config.load_config() serialised with --config
probes target.probe.jlink.list_connected_probes() unless --no-probes
ports transport.ports.list_serial_ports() unless --no-ports
host platform.system(), .release(), .machine(), .python_version(), sys.platform always, in the manifest

The dependency collector reuses the pipeline’s own provenance reader; it never parses nsx.lock itself and never resolves, synchronises or mutates a workspace. The hostname (platform.node()) and any username are deliberately not collected: neither helps diagnose a toolchain or build problem and both identify a person more than the default redaction is designed to catch.

Models, firmware and generated sources, ELF and binary outputs, raw proprietary payloads, credentials and secret environment values are never read, not merely redacted afterwards. The archive writer and verify_support_bundle() enforce one allow-list: every member is named exactly nsx.lock or ends in .json, and an absolute path, a .. segment, a backslash, a NUL byte or a duplicate name is rejected before any bytes are trusted.

diagnostics/redact.py is conservative and deterministic. Absolute paths keep only their final component, except a path that is itself a home directory, whose final component is the account name. URL credentials are stripped and every query value is redacted except a short allow-list of plainly harmless names; file:// paths go through path redaction. Known token shapes (GitHub PAT, AWS access key, Slack token, JWT, HTTP bearer) are replaced wherever they appear, including inside a URL. Secret-looking KEY=VALUE and KEY: VALUE text is replaced, and any JSON mapping value whose key looks secret-shaped is always replaced regardless of the value. Device serial numbers are redacted structurally, by field name (serial, serial_number), and then by literal substitution wherever the same value recurs (a USB hwid string, a device path derived from it), so a sibling field cannot leak what structural redaction caught. --raw-probe-ids is the one opt-out; it keeps real probe and port serials and prints a warning.

Every pass returns a RedactionCounts; the manifest’s redaction object sums them so a reviewer sees which categories were found and rewritten, and how often. That is an audit trail of what redaction did, not a certificate that nothing sensitive remains: review a bundle before sharing it as you would any diagnostic output.

SupportBundleManifest (hpx.support-bundle-manifest, schema version 1) is a distinct contract from the run’s ResultManifest: a support bundle has no run status, validity or comparability. It reuses ResultArtifact for its member entries because that shape (path, size, SHA-256) is the same either way, and carries the host record, the section availability and reasons, the redaction counts and a generated_at timestamp.

write_support_bundle() writes a ZIP_STORED archive: members sorted, manifest.json last, a fixed 1980-01-01 timestamp and create_system = 0 on every entry, so the archive encodes neither the host clock nor its operating system. Storage is uncompressed because DEFLATE’s exact bytes depend on the zlib build doing the compressing, and the members are small JSON. The file name comes from content_fingerprint(), a SHA-256 over every other member’s name and digest, excluding manifest.json because its timestamp differs run to run. Two bundles from identical inputs therefore have byte-identical members and the same name, so a diff between two bundles is a diff between two environments. verify_support_bundle() re-derives every artifact’s size and digest, requires the declared and actual member sets to match exactly, and applies the member-name rules above before trusting anything else in the archive.

Block Path under src/helia_profiler/
collectors, archive, verify diagnostics/support_bundle.py
redaction diagnostics/redact.py
manifest model results/support_bundle.py
CLI entry cli/inspect_cmds.py (hpx doctor --bundle)
sources reused hostenv/doctor.py, deps/compatibility.py, deps/dependencies.py, target/probe/jlink.py, transport/ports.py

Troubleshooting for when to produce a bundle and what to attach with it; the issue codes for the run-side diagnostics the bundle does not carry.