Skip to content

fix(bundler): decode a downloaded (non-zip) bundle manifest as UTF-8 - #4190

Open
Noor-ul-ain001 wants to merge 1 commit into
github:mainfrom
Noor-ul-ain001:fix/download-remote-manifest-utf16
Open

fix(bundler): decode a downloaded (non-zip) bundle manifest as UTF-8#4190
Noor-ul-ain001 wants to merge 1 commit into
github:mainfrom
Noor-ul-ain001:fix/download-remote-manifest-utf16

Conversation

@Noor-ul-ain001

Copy link
Copy Markdown
Contributor

Summary

_download_remote_manifest's non-zip branch (src/specify_cli/commands/bundle/__init__.py) fed the downloaded bytes straight to yaml.safe_load(io.BytesIO(raw)). PyYAML's Reader auto-detects a UTF-16 BOM on a byte stream, so a well-formed UTF-16 bundle.yml (a realistic PowerShell Out-File/> output) was silently accepted — while yamlio.load_yaml decodes local sources strictly as UTF-8 and rejects the identical content with "Could not read ...".

BEFORE: a UTF-16 manifest downloaded via `bundle info`/`install`
  parses successfully — exit code 0, no warning.
AFTER: rejected with "... could not be read: ..." — exit code 1,
  matching local directory and .zip sources.

This is the same divergence, in the sibling branch of the same function, that was just fixed for the .zip case in commit 56aec8a (#3958): "feeding PyYAML the byte stream let its Reader honour a UTF-16 BOM and accept a manifest yamlio.load_yaml rejects, so zip and directory sources diverged." That fix covered _local_manifest_source's .zip branch, which this same function (_download_remote_manifest) calls for zip artifacts a few lines above — but the direct raw-YAML-download branch right after it had the identical unfixed bug.

Also drops the now-unused import io from the function.

Test plan

  • Added test_bundle_info_rejects_utf16_remote_manifest_like_local_sources to tests/contract/test_bundle_cli.py, downloading a UTF-16-encoded manifest via a mocked open_url and asserting bundle info --json fails with a "could not be read" error
  • Verified the new test fails without the fix (exit_code == 0, silently accepted) and passes with it
  • pytest tests/contract/test_bundle_cli.py — 43 passed, 1 pre-existing failure unrelated to this change (test_build_escapes_markup_in_output_path, a Windows path-separator/markup quirk, reproduces identically on unmodified upstream/main)
  • pytest tests/integration/test_bundler_local_install.py — 17 passed (no regression in the sibling .zip fix)
  • ruff check on both changed files — clean

🤖 Generated with Claude Code

_download_remote_manifest's non-zip branch fed the downloaded bytes
straight to `yaml.safe_load(io.BytesIO(raw))`. PyYAML's Reader
auto-detects a UTF-16 BOM on a byte stream, so a well-formed UTF-16
bundle.yml (a realistic PowerShell `Out-File`/`>` output) was silently
*accepted* here, while `yamlio.load_yaml` decodes local sources strictly
as UTF-8 and rejects the identical content with "Could not read ...".

  BEFORE: a UTF-16 manifest downloaded via `bundle info`/`install`
  parses successfully -- exit code 0, no warning.
  AFTER: rejected with "... could not be read: ..." -- exit code 1,
  matching local directory and .zip sources.

This is the same divergence, in the sibling branch of the same function,
that was just fixed for the .zip case in commit 56aec8a (PR github#3958):
"feeding PyYAML the byte stream let its Reader honour a UTF-16 BOM and
accept a manifest yamlio.load_yaml rejects, so zip and directory sources
diverged." That fix covered `_local_manifest_source`'s `.zip` branch
(which this same function calls for zip artifacts); the direct
raw-YAML-download branch a few lines below it had the identical bug.

Also drops the now-unused `import io` from this function.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@Noor-ul-ain001
Noor-ul-ain001 requested a review from mnriem as a code owner August 18, 2026 16:48
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.

1 participant