Skip to content

fix(rovodev): guard non-string prompt names when merging prompts.yml - #4145

Open
jawwad-ali wants to merge 1 commit into
github:mainfrom
jawwad-ali:fix/rovodev-nonstring-prompt-name
Open

fix(rovodev): guard non-string prompt names when merging prompts.yml#4145
jawwad-ali wants to merge 1 commit into
github:mainfrom
jawwad-ali:fix/rovodev-nonstring-prompt-name

Conversation

@jawwad-ali

Copy link
Copy Markdown
Contributor

Problem

RovodevIntegration._read_prompts_yml documents its contract:

Returns an empty list if the file is missing, malformed, or contains no valid prompt entries.

But it filters only at the entry level (isinstance(item, dict)) — it never validates the entry's name. _merge_prompt_entries then does:

name = entry.get("name", "")
if name in generated_by_name:      # <-- dict membership test

.rovodev/prompts.yml is user-editable, so a YAML sequence or mapping name reaches that unhashable-key lookup.

Reproduction on current main (bf88c9f)

name=list     -> TypeError: unhashable type: 'list'
name=mapping  -> TypeError: unhashable type: 'dict'
name=int      -> OK
name=null     -> OK

Only the unhashable shapes crash — the classic gap this repo's shape-guard vein targets.

The failure escapes setup() as a raw traceback, so every specify init, integration install and integration upgrade for rovodev on that project aborts, and the user's prompts.yml is never rewritten.

Fix

A non-string name can never match a generated entry, so treat it like any other unmatched entry and preserve it verbatim:

if not isinstance(name, str):
    merged.append(entry)
    continue

Verified all shapes now succeed with the user's entry intact:

name=list          -> OK   user entry preserved=True  total=11
name=mapping       -> OK   user entry preserved=True  total=11
name=int           -> OK   user entry preserved=True  total=11
name=null          -> OK   user entry preserved=True  total=11
name=matching str  -> OK   user entry preserved=True  total=10   <-- still merges

No breaking change. A string name takes the identical replace-in-place path (the matching case still collapses to 10 entries rather than 11). The only inputs whose behaviour changes are ones that previously crashed.

Verification

  • Fail-before / pass-after: 2 new-vs-baseline failures with the source reverted → 16 passed with the fix.
  • Parametrized over both unhashable shapes (sequence, mapping).
  • Scoped regression over tests/integrations: no new failures vs a clean-main baseline captured on bf88c9f9 (18 pre-existing, Windows symlink-privilege).
  • uvx ruff@0.15.0 check src tests → clean

Written with assistance from Claude Code. Bug found, reproduced, and verified by me on current main.

`_read_prompts_yml` documents that it "Returns an empty list if the file is
missing, malformed, or contains no valid prompt entries", but it only
filters at the entry level (`isinstance(item, dict)`) — it never validates
the entry's `name`.

`_merge_prompt_entries` then does `name = entry.get("name", "")` followed by
`if name in generated_by_name:`, a dict membership test. A hand-edited
`.rovodev/prompts.yml` whose entry has a YAML sequence or mapping `name`
therefore raises an unhandled TypeError out of setup():

  name=list     -> TypeError: unhashable type: 'list'
  name=mapping  -> TypeError: unhashable type: 'dict'
  name=int      -> OK
  name=null     -> OK

Only the unhashable shapes crash. Every `specify init` /
`integration install` / `integration upgrade` for rovodev on that project
then aborts with a raw traceback, and the user's prompts.yml is never
rewritten.

A non-string name can never match a generated entry, so treat it like any
other unmatched entry and preserve it verbatim.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jawwad-ali
jawwad-ali requested a review from mnriem as a code owner August 15, 2026 15:56
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.

1 participant