fix(bundler): validate a catalog source before persisting it - #4535
jawwad-ali wants to merge 1 commit into
Conversation
`add_source` wrote the new entry and only then constructed it:
catalogs.append(entry)
_write(project_root, catalogs)
return CatalogSource.from_dict(entry, Scope.PROJECT)
`CatalogSource.from_dict` already rejects an empty id, but by the time it ran
the entry was on disk. A whitespace-only `--id` (or a url from which no id can
be derived) therefore reported an error while leaving a broken entry behind:
add_source -> raised: A catalog source is missing its 'id'.
AFTER : [{"id": "", "url": "https://example.test/c.json", ...}]
The user sees a failure and reasonably assumes nothing happened. In fact the
project's catalog config is now unusable -- every command that loads the stack
fails on that entry:
load_source_stack -> BROKEN: BundlerError A catalog source is missing its 'id'.
so the project stays wedged until bundle-catalogs.yml is hand-edited.
Constructing before writing reuses the validation that already exists; nothing
is persisted when it fails.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🟢 Approval recommended
The fix and regression coverage address the persistence issue with no unresolved blockers.
Pull request overview
Prevents invalid catalog sources from being persisted by validating them before writing configuration.
Changes:
- Validates
CatalogSourcebefore updating catalog configuration. - Adds regression coverage for rejected additions.
File summaries
| File | Description |
|---|---|
tests/unit/test_bundler_catalog_config.py |
Adds regression coverage for rejected additions. |
src/specify_cli/bundler/commands_impl/catalog_config.py |
Reorders validation before persistence. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 0
- Review effort level: Lite (auto)
Note
Copilot is running an experiment and ran this review at Lite.
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
mnriem
left a comment
There was a problem hiding this comment.
Please resolve conflicts
|
@mnriem — I went to resolve the conflicts, and I think this PR should be closed as superseded rather than rebased. The bug is structurally fixed on so anything The specific trigger no longer fails at all. On current That's a deliberate semantic change, and it means my regression test now fails against Happy to close this myself if you'd prefer — I've left it for you since you closed #4135 in the same situation. Thanks for the rewrite; it addresses the root cause more thoroughly than my reordering did. |
|
Closing it as per comment above |
Problem
add_sourcewrites the new entry and only then constructs it:CatalogSource.from_dictalready rejects an empty id — but by the time it runs, the entry is on disk.Reproduction on current
main(c173bf1)The command reports a failure, so the user reasonably assumes nothing happened.
It wedges the project
Every command that loads the catalog stack now fails on that stray entry:
So a single mistyped
catalog add --id " "leaves the project's catalog config unusable untilbundle-catalogs.ymlis hand-edited — by hand, for a file thebundle catalogcommands exist to manage.Fix
Construct before writing, reusing the validation that already exists rather than adding a second check that could drift:
After the fix:
Verification
upstream/main→ passing with the fix.load_source_stackstill succeeds — the latter is what the stray entry actually broke.tests/unit: 536 passed vs a clean-mainbaseline of 535 passed, same 2 pre-existing failures, none new.uvx ruff@0.15.0 check src tests→ cleanNo behaviour change for any input that previously succeeded. The reordered call is the same construction, just earlier, and it raises for exactly the input that already raised — the difference is that the failure no longer leaves a file behind.
Note on overlap: this touches
commands_impl/catalog_config.py, as does my #4533, but a different function (add_sourcevsremove_source). Happy to rebase whichever lands second.Written with assistance from Claude Code. Bug found, reproduced, and verified by me on current
main.🤖 Generated with Claude Code