Skip to content

fix(ci): report malformed CTK redistrib metadata instead of tracebacking - #2550

Open
LeSingh1 wants to merge 1 commit into
NVIDIA:mainfrom
LeSingh1:ci-fetch-ctk-malformed-metadata
Open

fix(ci): report malformed CTK redistrib metadata instead of tracebacking#2550
LeSingh1 wants to merge 1 commit into
NVIDIA:mainfrom
LeSingh1:ci-fetch-ctk-malformed-metadata

Conversation

@LeSingh1

@LeSingh1 LeSingh1 commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Problem

fetch_ctk_redistrib.main() wraps its whole body in a deliberate handler so the tool reports problems rather than crashing:

except (ValueError, KeyError, OSError, urllib.error.URLError, json.JSONDecodeError) as exc:
    print(f"ERROR: {exc}", file=sys.stderr)
    return 1

Several malformed-manifest shapes escape it, because the guards test for absence rather than type.

1. String-valued top-level keys are treated as components. Every real redistrib_*.json carries "release_date", "release_label", "release_product" at the top level, next to the component objects. metadata.get(component) returns a str for those, and the guard only rejects None:

component_info = metadata.get(component)
if component_info is None:
    raise KeyError(f"unknown CTK component {component!r}")

subdir_info = component_info.get(ctk_subdir)   # AttributeError on a str

2. A manifest that is valid JSON but not an object. .github/actions/fetch_ctk/action.yml:77 downloads it with curl -LSs "$CTK_JSON_URL" -o "$CTK_JSON_FILE" — no --fail — so an HTTP error page or redirect body is written to the file. If it parses as null, an array, or a string, the failure surfaces several frames later.

3. A subdir entry that is a bare string rather than an object raises the same AttributeError from a different line.

Reproduced through main(argv) with --metadata-path (no network), against a manifest shaped like the real thing:

input result on main
--component cuda_nvcc (valid) rc=0 (correct)
--component not_a_component rc=1, ERROR: "unknown CTK component..." (correct)
--component release_label AttributeError: 'str' object has no attribute 'get'
metadata is null TypeError: argument of type 'NoneType' is not iterable
metadata is a JSON array AttributeError: 'list' object has no attribute 'get'
subdir entry is a string AttributeError: 'str' object has no attribute 'get'

The contrast with the absent-component row is the point: the tool already knows how to report this class of problem, and these paths bypass it.

Fix

  • Validate at load time that the manifest is a JSON object, with a message naming the source (path or URL).
  • Check that a component entry and its subdir entry are objects before reaching into them, with a message that says what was found instead.
  • In filter_components, replace ctk_subdir in metadata.get(resolved_component, {}) with an explicit isinstance(..., dict) check. On a string value that in silently degrades from a key lookup to a substring test — it happens to return False for the real keys today, but only by luck.

Valid manifests are unaffected: the component lookup, the subdir lookup, and the relative_path extraction all behave exactly as before.

Tests

This tool had no tests. Added ci/tools/tests/test_fetch_ctk_redistrib.py (which is already run by the nightly tooling job), driving main(argv) with --metadata-path so nothing touches the network:

  • a valid component resolves to its relative_path;
  • each of the three string-valued release keys is rejected with rc=1;
  • an absent component keeps its existing unknown CTK component message;
  • a non-object subdir entry is reported;
  • null / array / string manifests are reported for both subcommands;
  • filter-components skips a string-valued top-level key with the existing "Skipping unsupported CTK component" notice and rc=0.

Verification

Executed in full (pure Python, stdlib only, no GPU and no network):

# with the fix
pytest ci/tools/tests  ->  69 passed

# with ci/tools/fetch_ctk_redistrib.py restored from upstream/main
10 failed, 3 passed

ruff check / ruff format --check clean on both files; toolshed/check_spdx.py clean on the new file. Restore done with cp aside + git show upstream/main:<path> >; index verified clean before committing.

fetch_ctk_redistrib.main() wraps everything in a deliberate handler:

    except (ValueError, KeyError, OSError, urllib.error.URLError,
            json.JSONDecodeError) as exc:
        print(f"ERROR: {exc}", file=sys.stderr)
        return 1

Several malformed-manifest shapes escape it because the guards check for
absence, not type.

1. Real redistrib_*.json files carry string-valued top-level keys --
   "release_date", "release_label", "release_product" -- next to the
   component objects. `metadata.get(component)` returns a str for those, and
   `component_info is None` does not reject it, so `component_info.get(...)`
   raises `AttributeError: 'str' object has no attribute 'get'`.

2. The manifest is downloaded with `curl -LSs` and no `--fail`
   (.github/actions/fetch_ctk/action.yml), so an error page or redirect body
   is written to the file. If that body is valid JSON but not an object, the
   failure surfaces frames later as `TypeError: argument of type 'NoneType'
   is not iterable` or `AttributeError: 'list' object has no attribute 'get'`.

3. A subdir entry that is a bare string rather than an object raises the same
   AttributeError from a different line.

Reproduced via `main(argv)` with `--metadata-path`, so no network:

    --component release_label -> AttributeError: 'str' object has no attribute 'get'
    metadata is null          -> TypeError: argument of type 'NoneType' is not iterable
    metadata is a JSON array  -> AttributeError: 'list' object has no attribute 'get'
    subdir entry is a string  -> AttributeError: 'str' object has no attribute 'get'

An absent component, by contrast, already returns 1 with a clear message.

Validate the manifest is a JSON object where it is loaded, and check the
component and subdir entries are objects before reaching into them. Also
replace `ctk_subdir in metadata.get(resolved_component, {})` in
filter_components with an explicit dict check: on a string value that `in`
silently becomes a substring test rather than a key lookup.

Adds the first tests for this tool.
@copy-pr-bot

copy-pr-bot Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions github-actions Bot added the CI/CD CI/CD infrastructure label Aug 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI/CD CI/CD infrastructure

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant