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

`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
#3746/#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.


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

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Noor ul ain
2026-07-28 21:24:19 +05:00
committed by GitHub
parent a482fb2fce
commit 8bfe6e14d1
4 changed files with 86 additions and 7 deletions

View File

@@ -801,8 +801,9 @@ def extension_search(
# Metadata
console.print(f"\n [dim]Author:[/dim] {_escape_markup(str(ext.get('author', 'Unknown')))}")
if ext.get('tags'):
tags_str = ", ".join(str(t) for t in ext['tags'])
ext_tags = ext.get('tags', [])
if isinstance(ext_tags, list) and ext_tags:
tags_str = ", ".join(str(t) for t in ext_tags)
console.print(f" [dim]Tags:[/dim] {_escape_markup(tags_str)}")
# Source catalog
@@ -1025,8 +1026,9 @@ def _print_extension_info(ext_info: dict, manager):
console.print()
# Tags
if ext_info.get('tags'):
tags_str = ", ".join(str(t) for t in ext_info['tags'])
info_tags = ext_info.get('tags', [])
if isinstance(info_tags, list) and info_tags:
tags_str = ", ".join(str(t) for t in info_tags)
console.print(f"[bold]Tags:[/bold] {_escape_markup(tags_str)}")
console.print()

View File

@@ -2326,7 +2326,7 @@ def workflow_search(
if desc:
console.print(f" {_escape_markup(str(desc))}")
tags = wf.get("tags", [])
if tags:
if isinstance(tags, list) and tags:
safe_tags = _escape_markup(", ".join(str(t) for t in tags))
console.print(f" [dim]Tags: {safe_tags}[/dim]")
console.print()
@@ -2424,8 +2424,9 @@ def workflow_info(
console.print(f" Version: {_escape_markup(str(info.get('version', '?')))}")
if info.get("description"):
console.print(f" Description: {_escape_markup(str(info['description']))}")
if info.get("tags"):
safe_tags = _escape_markup(", ".join(str(t) for t in info["tags"]))
info_tags = info.get("tags", [])
if isinstance(info_tags, list) and info_tags:
safe_tags = _escape_markup(", ".join(str(t) for t in info_tags))
console.print(f" Tags: {safe_tags}")
console.print(" [yellow]Not installed[/yellow]")
else: