fix(workflows): guard a non-string overlay edit operation - #3881
Open
jawwad-ali wants to merge 1 commit into
Open
fix(workflows): guard a non-string overlay edit operation#3881jawwad-ali wants to merge 1 commit into
operation#3881jawwad-ali wants to merge 1 commit into
Conversation
`_parse_edit` reads `operation` straight from hand-edited YAML and then
does `if operation not in VALID_OPERATIONS`. `VALID_OPERATIONS` is a
frozenset, so that membership test hashes the value — and an unhashable
one raises:
operation={'insert_after': 'a'} -> TypeError: unhashable type: 'dict'
operation=['insert_after'] -> TypeError: unhashable type: 'list'
`validate_overlay_yaml`'s docstring promises "validation never raises",
and nothing upstream catches TypeError (layer_sources wraps only
YAMLError/OSError/UnicodeDecodeError; _commands catches only ValueError),
so the CLI dies with a raw traceback instead of reporting the error.
The trigger is an ordinary authoring mistake: nesting the recommended
shorthand form under the explicit key.
Every other field in the same function is isinstance-guarded first
(`anchor`, `step`, `step["id"]`); `operation` was the outlier. Check the
type first and return the message the function already uses for
`operation: None` / `operation: 7`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Prevents malformed YAML overlay operations from raising TypeError, preserving the validator’s never-raise contract.
Changes:
- Type-checks
operationbefore set membership. - Adds regression tests for mapping and sequence values.
Show a summary per file
| File | Description |
|---|---|
src/specify_cli/workflows/overlays/schema.py |
Safely rejects non-string operations. |
tests/workflows/test_overlay_schema.py |
Covers previously unhashable operation values. |
Review details
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 2/2 changed files
- Comments generated: 0
- Review effort level: Balanced
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
_parse_editinsrc/specify_cli/workflows/overlays/schema.pyreadsoperationstraight from hand-edited YAML, then membership-tests it:VALID_OPERATIONSis afrozenset, so that test hashes the value. An unhashable one raises instead of being reported.Reproduction on current
main(81bf741)Why this is a contract violation, not just an edge case
validate_overlay_yaml's own docstring states:And nothing upstream catches
TypeError—layer_sourceswraps onlyyaml.YAMLError/OSError/UnicodeDecodeError, and_commands.pycatches onlyValueError. So the raw traceback reaches the user.The trigger is an ordinary authoring mistake — nesting the recommended shorthand form under the explicit key:
Every other field in the same function is isinstance-guarded before use:
anchorschema.py:93—if not isinstance(anchor, str) or not anchor:stepschema.py:102—if not isinstance(step, dict):step["id"]schema.py:105—if not isinstance(step_id, str) or not step_id:_validate_safe_idschema.py:49—if not isinstance(value, str) or not value:operationFix
One code line, matching the sibling guards:
No breaking change. The only inputs whose behaviour changes are the ones that raise
TypeErrortoday; they now return the same"Edit at index N has invalid operation ..."message the function already produces foroperation: Noneoroperation: 7. Every currently-valid and currently-cleanly-rejected input is byte-identical.Scope note
merge.py:384has the sameedit.operation not in VALID_OPERATIONSpattern, but anOverlayEditcan only be constructed by_parse_edit, so once this guard is in place that path is unreachable. I left it alone to keep the PR to one file — happy to add a defensive guard there too if you'd prefer.Verification
src(TypeError) and pass with the fix. File: 2 failed → 33 passed.tests/workflows: failure set identical to the clean-mainbaseline I captured on81bf741(10 pre-existing in scope, all Windows symlink-privilege).uvx ruff@0.15.0 check src tests→ cleanTest goes into the existing
TestShorthandEdits, besidetest_invalid_operation_field_rejected.Written with assistance from Claude Code. Bug found, reproduced, and verified by me on current
main.