fix(presets): coerce non-string catalog tags before joining (#3743)

* fix(presets): coerce non-string catalog tags before joining

Preset catalog payloads are user-editable YAML/JSON, so a `tags:` list
can legitimately contain non-strings (e.g. numeric tags). The preset
list/search/info display paths and the catalog search backend joined
tags with a raw `", ".join(...)` / used `t.lower()`, which raised
`TypeError: sequence item N: expected str instance, int found` (or
`AttributeError` on `.lower()`) and crashed the command.

Sibling command surfaces already guard this — extensions, integrations,
and workflows coerce with `str(t) for t in ...`. This aligns presets:

- `_commands.py`: `preset list`, `preset search`, and both `preset info`
  branches now join `str(t) for t in ...`.
- `__init__.py` `PresetCatalog.search`: tag filter uses `str(t).lower()`
  and the searchable-text join coerces tags to `str`.

Adds regression tests driving `preset search` and `preset info` through
CliRunner with numeric tags; both fail before the fix with the TypeError.

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

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Noor ul ain
2026-07-28 00:14:59 +05:00
committed by GitHub
parent 683bfd00c9
commit 6136706ef3
3 changed files with 73 additions and 6 deletions

View File

@@ -4504,7 +4504,7 @@ class PresetCatalog:
continue
if tag and tag.lower() not in [
t.lower() for t in pack_data.get("tags", [])
str(t).lower() for t in pack_data.get("tags", [])
]:
continue
@@ -4516,7 +4516,7 @@ class PresetCatalog:
pack_data.get("description", ""),
pack_id,
]
+ pack_data.get("tags", [])
+ [str(t) for t in pack_data.get("tags", [])]
).lower()
if query_lower not in searchable_text:

View File

@@ -61,7 +61,7 @@ def preset_list():
console.print(f" [bold]{pack['name']}[/bold] ({pack['id']}) v{pack['version']}{status} — priority {pri}")
console.print(f" {pack['description']}")
if pack.get("tags"):
tags_str = ", ".join(pack["tags"])
tags_str = _escape_markup(", ".join(str(t) for t in pack["tags"]))
console.print(f" [dim]Tags: {tags_str}[/dim]")
console.print(f" [dim]Templates: {pack['template_count']}[/dim]")
console.print()
@@ -288,7 +288,7 @@ def preset_search(
console.print(f" [bold]{pack.get('name', pack['id'])}[/bold] ({pack['id']}) v{pack.get('version', '?')}")
console.print(f" {pack.get('description', '')}")
if pack.get("tags"):
tags_str = ", ".join(pack["tags"])
tags_str = ", ".join(str(t) for t in pack["tags"])
console.print(f" [dim]Tags: {tags_str}[/dim]")
console.print()
@@ -379,7 +379,7 @@ def preset_info(
if local_pack.author:
console.print(f" Author: {local_pack.author}")
if local_pack.tags:
console.print(f" Tags: {', '.join(local_pack.tags)}")
console.print(f" Tags: {', '.join(str(t) for t in local_pack.tags)}")
console.print(f" Templates: {len(local_pack.templates)}")
for tmpl in local_pack.templates:
console.print(f" - {tmpl['name']} ({tmpl['type']}): {tmpl.get('description', '')}")
@@ -415,7 +415,7 @@ def preset_info(
if pack_info.get("author"):
console.print(f" Author: {pack_info['author']}")
if pack_info.get("tags"):
console.print(f" Tags: {', '.join(pack_info['tags'])}")
console.print(f" Tags: {', '.join(str(t) for t in pack_info['tags'])}")
if pack_info.get("repository"):
console.print(f" Repository: {pack_info['repository']}")
if pack_info.get("license"):