fix(bundler): report a newline-containing version constraint instead of crashing - #4538
jawwad-ali wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
🟢 Approval recommended
The focused guard fixes the crash while tests cover both regression and unchanged behavior.
Pull request overview
Prevents malformed multiline version constraints from leaking AttributeError, preserving the bundler’s error contract.
Changes:
- Raises
InvalidSpecifierfor unmatched clauses. - Adds regression and compatibility tests.
- Fail-before evidence was provided but not independently rerun.
File summaries
| File | Description |
|---|---|
src/specify_cli/bundler/lib/versioning.py |
Routes malformed multiline constraints through BundlerError. |
tests/unit/test_bundler_versioning.py |
Covers multiline failures and valid constraints. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 0
- Review effort level: Balanced
💡 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
…of crashing
`_normalize_constraint` matched each comma-separated clause and used the result
without checking it:
match = _SPECIFIER_CLAUSE.match(raw)
operator, version = match.groups()
`_SPECIFIER_CLAUSE` is anchored with `^`/`$` and `.` does not cross newlines, so
a clause containing an EMBEDDED newline does not match at all and
`match.groups()` raised a raw AttributeError -- escaping `parse_constraint`'s
contract to surface bad input as a BundlerError.
A YAML block literal reaches this with no exotic input:
requires:
speckit_version: |
>=1.0.0
<2.0.0
loads as ">=1.0.0\n<2.0.0\n", and:
parse_constraint -> AttributeError: 'NoneType' object has no attribute 'groups'
satisfies -> AttributeError: 'NoneType' object has no attribute 'groups'
Note leading/trailing newlines were already fine (`\s*` absorbs them) -- only an
embedded one fails, which is why this survived.
Now raises InvalidSpecifier for an unmatched clause, which the existing handler
in `parse_constraint` converts to the BundlerError callers expect.
Rebased onto current main (files moved in the workflow/bundler restructure).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2b15784 to
7952e68
Compare
|
@mnriem Conflicts resolved (7952e68). The fix now lives at Verified: 3 new cases fail with the source reverted to Rebased as a single commit on current |
Problem
_normalize_constraintmatches each comma-separated clause and uses the result without checking it:_SPECIFIER_CLAUSEis anchored with^/$, and.does not cross newlines — so a clause containing an embedded newline does not match at all, andmatch.groups()raises a rawAttributeError. That escapesparse_constraint's contract, which is to surface bad input as aBundlerError:Reproduction on current
main(c173bf1)A YAML block literal — an ordinary way to write a multi-clause constraint if you don't know the comma form — reaches it directly:
loads as
'>=1.0.0\n<2.0.0\n', and:Worth noting why this survived: leading and trailing newlines are fine, because
\s*absorbs them. Only an embedded one fails:Fix
Raise
InvalidSpecifierfor an unmatched clause, which the existing handler inparse_constraintalready converts to theBundlerErrorcallers expect — so the fix reuses the established error path rather than introducing a second one.After the fix:
Verification
upstream/main→ 33 passed with the fix.tests/unit: 543 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 only inputs affected are those that previously crashed with an
AttributeError, which now report the documented error instead.Written with assistance from Claude Code. Bug found, reproduced, and verified by me on current
main.🤖 Generated with Claude Code