fix(workflows): guard a non-string overlay edit 'operation' (#3881)

`_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>
This commit is contained in:
Ali jawwad
2026-08-10 13:19:50 -05:00
committed by GitHub
co-authored by Claude Opus 5
parent fd8cc6da22
commit 3451a21277
2 changed files with 44 additions and 1 deletions
+6 -1
View File
@@ -87,7 +87,12 @@ def _parse_edit(edit_raw: dict[str, Any], idx: int) -> tuple[OverlayEdit | None,
else:
return None, f"Edit at index {idx} has no operation; expected one of {sorted(VALID_OPERATIONS)}."
if operation not in VALID_OPERATIONS:
# ``operation`` comes straight from hand-edited YAML, so it may be an
# unhashable mapping/sequence (``operation: {insert_after: a}`` when the
# shorthand form is nested by mistake). Membership-testing an unhashable
# value against the frozenset raises TypeError, which would escape this
# never-raising validator; check the type first, like 'anchor' below.
if not isinstance(operation, str) or operation not in VALID_OPERATIONS:
return None, f"Edit at index {idx} has invalid operation {operation!r}."
if not isinstance(anchor, str) or not anchor: