# Files changed · PR \#369 · lucenx/codexbar-plasma

[View on GitCafe](https://git.cafe/lucenx/codexbar-plasma/pull/369/diffs)

Repository: [lucenx/codexbar-plasma](https://git.cafe/lucenx/codexbar-plasma)

Visibility: public

[fix\(managed-cli\): map undecodable staged files to unverified](https://git.cafe/lucenx/codexbar-plasma/pull/369.md)

Version: 1

Head commit: a5f0987cd55a5f3b8eef73ac58e152c08d87cb4a

Base commit: ab12fc8cf001c27d7c51a2345d344598b58e768f

Comparison base: ab12fc8cf001c27d7c51a2345d344598b58e768f

Showing up to 20 entries from this revision. If the pull request changes, reopen the first page before continuing.

## CHANGELOG.md

Status: modified; +2; −0

```diff
@@ -15,6 +15,8 @@
 - Harden the TODO gate helper: it refuses Decisions redirects instead of
   following them with the API key, rejects oversized responses, and bounds
   its git probes with a timeout.
+- Report staged checksum and VERSION files that are not decodable text as
+  unverified instead of a generic error, matching other refused downloads.
 - Keep the stored AI Insights model when opening settings. Opening and saving
   the page no longer clears the model, so AI Insights stops asking for
   reconfiguration when nothing was changed. The stored model is also offered
```

## scripts/lib/managed_cli.py

Status: modified; +10; −2

```diff
@@ -310,12 +310,20 @@
         download(assets[0], archive)
         download(assets[1], checksum)
         expected = assets[0]["digest"][7:]
+        try:
+            checksum_text = checksum.read_text()
-        if checksum.read_text().strip() not in (expected + "  " + assets[0]["name"], expected + " *" + assets[0]["name"]):
+        except UnicodeDecodeError:
+            raise ValueError("checksum_file") from None
+        if checksum_text.strip() not in (expected + "  " + assets[0]["name"], expected + " *" + assets[0]["name"]):
             raise ValueError("checksum_file")
         extracted = stage / "release"
         extracted.mkdir()
         unpack(archive, extracted)
+        try:
+            staged_version = (extracted / "VERSION").read_text()
-        if (extracted / "VERSION").read_text().strip() not in (version, "v" + version):
+        except UnicodeDecodeError:
+            raise ValueError("version_file") from None
+        if staged_version.strip() not in (version, "v" + version):
             raise ValueError("version_file")
         home = stage / "probe-home"
         home.mkdir()
```

## tests/test_managed_cli.py

Status: modified; +15; −2

```diff
@@ -17,10 +17,10 @@
 from lib import managed_cli as cli
 
 
-def archive(path, version="0.62.0", extra=None, banner=None):
+def archive(path, version="0.62.0", extra=None, banner=None, version_bytes=None):
     with tarfile.open(path, "w:gz") as bundle:
         for name, raw in [("CodexBarCLI", ('#!/bin/sh\nprintf "CodexBar ' + (banner or version) + '\\n"\n').encode()),
+                          ("VERSION", version_bytes if version_bytes is not None else version.encode())]:
-                          ("VERSION", version.encode())]:
             item = tarfile.TarInfo(name)
             item.size = len(raw)
             bundle.addfile(item, io.BytesIO(raw))
@@ -156,6 +156,19 @@
             self.operation("update")
         self.assertEqual(cli.status(self.root)["version"], "0.62.0")
 
+    def test_binary_staged_files_report_unverified(self):
+        self.operation("install")
+        self.publish("0.63.0")
+        self.payload.with_suffix(".sha256").write_bytes(b"\xff\xfe\x00binary")
+        with self.assertRaisesRegex(ValueError, "checksum_file") as refused:
+            self.operation("update")
+        self.assertEqual(cli.failure_status(refused.exception), "unverified")
+        self.publish("0.63.0", version_bytes=b"\xff\xfe")
+        with self.assertRaisesRegex(ValueError, "version_file") as refused:
+            self.operation("update")
+        self.assertEqual(cli.failure_status(refused.exception), "unverified")
+        self.assertEqual(cli.status(self.root)["version"], "0.62.0")
+
     def test_abandoned_download_is_pruned_before_a_failed_retry(self):
         cli.private_directory(self.root)
         abandoned = self.root / ".install-abandoned"
```
