Skip to content

fix(workflows,extensions): tolerate non-list catalog tags in search/info display - #3770

Merged
mnriem merged 1 commit into
github:mainfrom
Noor-ul-ain001:fix/nonlist-tags-display-workflows-extensions
Jul 28, 2026
Merged

fix(workflows,extensions): tolerate non-list catalog tags in search/info display#3770
mnriem merged 1 commit into
github:mainfrom
Noor-ul-ain001:fix/nonlist-tags-display-workflows-extensions

Conversation

@Noor-ul-ain001

Copy link
Copy Markdown
Contributor

Problem

workflow search, workflow info, extension search and extension info crash with a raw traceback when a catalog entry carries a scalar tags: value:

wf-a:
  name: Workflow A
  tags: 5        # valid YAML, not a list
workflow search -> TypeError: 'int' object is not iterable
workflow info   -> TypeError: 'int' object is not iterable
extension search -> TypeError: 'int' object is not iterable
extension info   -> TypeError: 'int' object is not iterable

Catalog payloads are user-editable YAML/JSON, so this shape reaches the display unvalidated.

Interestingly, both backends already handle this correctly. WorkflowCatalog.search and ExtensionCatalog.search gate their tag filter on isinstance(raw_tags, list), so --tag ci skips the malformed entry cleanly. Only the display paths were unguarded — they tested truthiness and then iterated:

if info.get("tags"):                                   # truthy for 5
    safe_tags = ", ".join(str(t) for t in info["tags"]) # boom

So filtering worked while plain rendering crashed.

Why the existing str(t) coercion doesn't cover this

This is a distinct failure mode from the non-string element handling added in #3746 / #3747. Coercing elements with str(t) for t in ... does nothing when tags isn't a sequence at all — the iteration itself is what fails.

Fix

Use the guard the sibling integration commands already use. integrations/_query_commands.py:332,402 gate on isinstance(tags, list) and tags; this applies the same pattern to workflows and extensions rather than introducing a new idiom.

A repo-wide audit of every ", ".join(str(t) for t in ...) tag-display site confirms the four sites in these two modules were the only unguarded ones outside presets (which are covered separately in #3769):

Site Before After
extensions/_commands.py search unguarded guarded
extensions/_commands.py _print_extension_info unguarded guarded
workflows/_commands.py workflow_search unguarded guarded
workflows/_commands.py workflow_info unguarded guarded
integrations/_query_commands.py ×2 already guarded unchanged

Tests

One regression test per module, each covering search and info, driving the full CLI via CliRunner:

  • TestWorkflowCliAlignment::test_search_and_info_tolerate_non_list_tags
  • TestExtensionCatalog::test_search_and_info_tolerate_non_list_tags

Both fail before the fix with the exact target exception, verified by stashing the source changes:

FAILED tests/test_workflows.py::TestWorkflowCliAlignment::test_search_and_info_tolerate_non_list_tags
FAILED tests/test_extensions.py::TestExtensionCatalog::test_search_and_info_tolerate_non_list_tags
  → where 1 = <Result TypeError("'int' object is not iterable")>.exit_code
2 failed

tests/test_workflows.py tests/test_extensions.py after the fix: 982 passed, 17 failed, 113 errors. Clean main gives 980 passed with an identical 17 failed / 113 errors — those are pre-existing Windows-local issues (symlink cases needing elevation, and a locked pytest temp root). Net effect is +2 passing, no new failures.

🤖 Generated with Claude Code

…nfo display

`workflow search`, `workflow info`, `extension search` and `extension info`
crashed with `TypeError: 'int' object is not iterable` when a catalog entry
carried a scalar `tags:` value (e.g. `tags: 5`). Catalog payloads are
user-editable YAML/JSON, so this shape reaches the display unvalidated.

Both backends already guard their tag *filter* with
`isinstance(raw_tags, list)` — `WorkflowCatalog.search` and
`ExtensionCatalog.search` skip a non-list `tags` cleanly. Only the display
paths were unguarded: they tested truthiness (`if info.get("tags"):`) and
then iterated. A scalar is truthy but not iterable, so `--tag` filtering
survived while plain `search`/`info` rendering blew up.

Note this is distinct from the non-string *element* handling added in
github#3746/github#3747: coercing elements with `str(t) for t in ...` does not help when
`tags` is not a sequence at all. The fix is the guard the sibling
integration commands already use — `integrations/_query_commands.py:332,402`
gate on `isinstance(tags, list) and tags`. This aligns workflows and
extensions with that reference pattern, leaving all four tag-join display
sites in these modules consistent.

Regression tests drive the full CLI via CliRunner and cover search + info in
one case per module; both fail before the fix with the exact TypeError.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Prevents workflow and extension search/info commands from crashing when catalog tags is not a list.

Changes:

  • Guard tag rendering with isinstance(tags, list).
  • Add CLI regression coverage for search and info paths.
Show a summary per file
File Description
src/specify_cli/workflows/_commands.py Safely renders workflow tags.
src/specify_cli/extensions/_commands.py Safely renders extension tags.
tests/test_workflows.py Tests scalar workflow tags.
tests/test_extensions.py Tests scalar extension tags.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 4/4 changed files
  • Comments generated: 0
  • Review effort level: Medium

@mnriem
mnriem merged commit 8bfe6e1 into github:main Jul 28, 2026
14 checks passed
@mnriem

mnriem commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Thank you!

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.

3 participants