Skip to content

Handle malformed/missing builds.json gracefully in refresh & validate_json#24

Merged
fnando merged 3 commits into
mainfrom
fix/graceful-json-error-handling
Jun 10, 2026
Merged

Handle malformed/missing builds.json gracefully in refresh & validate_json#24
fnando merged 3 commits into
mainfrom
fix/graceful-json-error-handling

Conversation

@sagpatil

@sagpatil sagpatil commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Two release-automation scripts could crash with an unhandled exception and a Python traceback instead of the project's clean common.die() / exit-1 path when builds.json (or builds.schema.json) is malformed or missing — the kind of state you get from a merge conflict or a hand-edit during a release. Both are small, mechanical fixes that route the failure into the error handling the code already intends.

These surfaced from a code review of the repo. Neither can corrupt a published image or pass a check that should fail — the impact is purely operator-facing log hygiene (traceback vs. clean error) — but validate_json in particular is the tool meant to catch a bad builds.json, so crashing on one is a poor failure mode.

Bug 1 — refresh.py: builds.load() outside its try block

main() called builds.load() / find_cli() one line above the try whose except ValueError / except OSError handlers were clearly meant to cover them — json.JSONDecodeError is a subclass of ValueError, and FileNotFoundError a subclass of OSError. Because the call sat outside the block, a malformed builds.json raised an uncaught JSONDecodeError instead of being reported via common.die().

Fix: move builds.load() / find_cli() inside the existing try. No new handler needed — the existing except ValueError/except OSError now cover it.

     cli = args.stellar_cli_version
-    data = builds.load()
-    entry = builds.find_cli(data, cli)

     try:
+        data = builds.load()
+        entry = builds.find_cli(data, cli)
         if args.rust_versions:

Bug 2 — validate_json.py: unguarded re-parse in main()

main() re-parsed builds.json and builds.schema.json with no guard. check_sorted_keys() does catch JSONDecodeError, but only logs and continues (it doesn't stop the run), so execution still reached the unguarded re-parse on the same file and crashed with a traceback.

Fix: guard the parse and return 1 with a clean error message, consistent with the rest of the script.

     builds_path = root / "builds.json"
     schema_path = root / "builds.schema.json"
-    builds_data = json.loads(builds_path.read_text())
-    schema = json.loads(schema_path.read_text())
+    try:
+        builds_data = json.loads(builds_path.read_text())
+        schema = json.loads(schema_path.read_text())
+    except (OSError, json.JSONDecodeError) as exc:
+        common.err(f"could not load builds.json/builds.schema.json: {exc}")
+        return 1

Testing

  • tests/integration/test_refresh.py12 passed.
  • Functional repro confirms both paths now degrade gracefully on a malformed/missing builds.json:
    • refresh.pySystemExit(1) via common.die() (clean stderr message), not a raw JSONDecodeError.
    • validate_json.py → guarded block catches FileNotFoundError and JSONDecodeError, returns 1.

Note: the validate_json unit suite couldn't be executed in my local sandbox — the only available local 3.14 interpreter is 3.14.0a5, on which jsonschema/attrs segfault at import (an alpha-CPython issue, unrelated to this change). It should run normally on CI's pinned 3.14.5. The guarded block was verified in isolation instead.

🤖 Generated with Claude Code

…_json

Two scripts could crash with an unhandled exception (and a Python
traceback) instead of the project's clean common.die()/exit-1 path when
builds.json is malformed or missing — e.g. after a merge conflict or a
hand-edit during a release.

refresh.py: builds.load() / find_cli() sat one line above the try block
whose `except ValueError` / `except OSError` handlers were clearly meant
to cover them (JSONDecodeError is a ValueError subclass; FileNotFoundError
an OSError). Move both calls inside the try so a bad builds.json is
reported via common.die() instead of escaping as a raw traceback.

validate_json.py: main() re-parsed builds.json/builds.schema.json with no
guard. check_sorted_keys() catches JSONDecodeError but only logs and
continues, so execution still reached the unguarded re-parse and crashed —
ironic for the very tool meant to validate that file. Guard the parse and
return 1 with a clean error.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 9, 2026 20:18

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR improves operator-facing failure behavior in the release automation scripts by ensuring missing/malformed builds.json (and schema) errors follow the project’s clean common.err() / exit-1 path instead of producing uncaught Python tracebacks.

Changes:

  • scripts/refresh.py: moves builds.load() / builds.find_cli() inside the existing try so malformed/missing builds.json is handled by existing except blocks.
  • scripts/validate_json.py: wraps parsing of builds.json / builds.schema.json in a guard to return 1 with a clean error message on read/parse failure.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
scripts/validate_json.py Adds guarded load/parse of builds.json and builds.schema.json to avoid unhandled exceptions.
scripts/refresh.py Ensures builds.json load/lookup happens inside existing error-handling try block.

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

Comment thread scripts/validate_json.py
Path.read_text() raises UnicodeDecodeError on a non-UTF-8 file, which is
a ValueError subclass and so was not caught by the (OSError,
json.JSONDecodeError) guard — it would still crash with a traceback.
Add it to the guard so a non-UTF-8 builds.json also exits 1 cleanly.

(refresh.py is already covered: its handler is the broader `except
ValueError`, of which UnicodeDecodeError is a subclass.)

Addresses Copilot review feedback on PR #24.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sagpatil
sagpatil requested a review from fnando June 9, 2026 20:22
@fnando
fnando enabled auto-merge (squash) June 10, 2026 00:02
@fnando
fnando merged commit 633a4ab into main Jun 10, 2026
12 checks passed
@fnando
fnando deleted the fix/graceful-json-error-handling branch June 10, 2026 00:02
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