Skip to content

fix(bundle): wrap malformed YAML in a local .zip bundle manifest - #4013

Merged
mnriem merged 1 commit into
github:mainfrom
Noor-ul-ain001:fix/bundle-zip-malformed-yaml
Aug 7, 2026
Merged

fix(bundle): wrap malformed YAML in a local .zip bundle manifest#4013
mnriem merged 1 commit into
github:mainfrom
Noor-ul-ain001:fix/bundle-zip-malformed-yaml

Conversation

@Noor-ul-ain001

Copy link
Copy Markdown
Contributor

Problem

_local_manifest_source accepts three local bundle sources. Two of them reach YAML through BundleManifest.from_fileload_yaml, which wraps a parse failure:

try:
    has_node = yaml.compose(text) is not None
    data = yaml.safe_load(text)
except yaml.YAMLError as exc:
    raise BundlerError(f"Invalid YAML in {path}: {exc}") from exc

The .zip branch parses inline instead, with no guard:

data = _yaml.safe_load(io.BytesIO(raw))
return BundleManifest.from_dict(data)

yaml.YAMLError derives directly from Exception — it is neither a ValueError nor an OSError:

yaml.error.YAMLError -> Exception -> BaseException -> object

So it slips past bundle_install's except BundlerError and surfaces as a raw traceback.

Reproduction

python -c "import zipfile; zipfile.ZipFile('bad.zip','w').writestr('bundle.yml','bundle: [unclosed\n  id: x\n')"
specify bundle install ./bad.zip

For the identical malformed bundle.yml, the three local sources disagree:

Source Result
./bundle-dir Error: Invalid YAML in bundle.yml: ... — exit 1
./bundle.yml Error: Invalid YAML in standalone.yml: ... — exit 1
./bundle.zip yaml.parser.ParserError traceback

Why this is the local path's bug, not a missing global handler

The remote counterpart of this exact call — _download_manifest, which parses a manifest fetched from a catalog — already handles it, and even names the exception explicitly:

except _yaml.YAMLError as exc:
    raise BundlerError(
        f"Downloaded content for bundle '{entry_id}' from {_source_desc} "
        f"is not valid YAML: {exc}"
    ) from exc

So the same corrupt manifest is reported cleanly when it arrives over the network but crashes when installed from disk. This fix brings the local branch in line with both its in-function siblings and its remote twin.

Tests

Two regression tests in tests/integration/test_bundler_local_install.py:

  • test_local_zip_wraps_malformed_manifest_yaml — pins the BundlerError contract on the zip branch.
  • test_malformed_manifest_yaml_fails_alike_for_every_local_source — drives all three local sources through the CLI and asserts they now fail identically. This is the one that encodes the actual invariant; on main it fails with artifact.zip leaked ParserError while the other two sources pass.

Both fail before the change and pass after. tests/integration/test_bundler_local_install.py + tests/contract/test_bundle_cli.py: 49 passed. ruff check clean.

🤖 Generated with Claude Code

`_local_manifest_source` handles three local bundle sources. The directory
and `bundle.yml` branches both go through `BundleManifest.from_file` ->
`load_yaml`, which converts a parse failure into a `BundlerError`. The `.zip`
branch instead parses inline with a bare `_yaml.safe_load`.

`yaml.YAMLError` derives directly from `Exception` -- it is neither a
`ValueError` nor an `OSError` -- so it escapes `bundle_install`'s
`except BundlerError` and reaches the user as a raw
`yaml.parser.ParserError` traceback.

The remote counterpart of this same call, `_download_manifest`, already
guards it and even names `_yaml.YAMLError` explicitly. Only the local zip
path was missed, so the same corrupt manifest is reported cleanly when
fetched from a catalog but crashes when installed from disk.

Before, for the identical malformed bundle.yml:

    specify bundle install ./bundle-dir   ->  Error: Invalid YAML in ... (exit 1)
    specify bundle install ./bundle.yml   ->  Error: Invalid YAML in ... (exit 1)
    specify bundle install ./bundle.zip   ->  ParserError traceback

Two regression tests: one pins the `BundlerError` contract on the zip
branch, and one drives all three local sources through the CLI to assert
they now fail alike.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Wraps malformed YAML errors from local ZIP bundle manifests in BundlerError, aligning ZIP installation with other local sources.

Changes:

  • Catches yaml.YAMLError when parsing ZIP manifests.
  • Adds unit and CLI regression coverage across all local source types.
Show a summary per file
File Description
src/specify_cli/commands/bundle/__init__.py Converts ZIP manifest YAML failures into actionable CLI errors.
tests/integration/test_bundler_local_install.py Verifies consistent malformed-YAML handling.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 2/2 changed files
  • Comments generated: 0
  • Review effort level: Balanced

@mnriem
mnriem merged commit 920ed75 into github:main Aug 7, 2026
14 checks passed
@mnriem

mnriem commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants