mirror of
https://github.com/github/spec-kit.git
synced 2026-08-03 06:26:30 +08:00
fix(agents): coerce a non-string description in TOML command rendering (#3799)
CommandRegistrar.render_toml_command passes the raw frontmatter `description`
straight into `_render_basic_toml_string`, which iterates the value and calls
ord() on each character. Frontmatter comes from yaml.safe_load, so description
can be any YAML type:
description='ok string' -> description = "ok string"
description=None -> TypeError: 'NoneType' object is not iterable
description=42 -> TypeError: 'int' object is not iterable
description=True -> TypeError: 'bool' object is not iterable
description=['a','b'] -> description = "ab" <- silently WRONG value
This is a format-branch asymmetry: it is the only renderer reached from
register_commands' format branches that does not normalise description.
render_yaml_command (same class, ~70 lines below) already does exactly
`if not isinstance(description, str): description = str(description) if
description is not None else ""`, render_markdown_command goes through
yaml.dump which handles any type, and TomlIntegration._extract_description
returns "" for a non-str. So only extension/preset commands rendered for the two
TOML agents were affected.
Apply the same coercion the sibling uses. After: None -> "", 42 -> "42",
True -> "True", ['a','b'] -> "['a', 'b']", each still valid parseable TOML.
String descriptions are untouched.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -302,8 +302,20 @@ class CommandRegistrar:
|
||||
toml_lines = []
|
||||
|
||||
if "description" in frontmatter:
|
||||
# Frontmatter comes from ``yaml.safe_load``, so ``description`` can
|
||||
# be any YAML type: ``description:`` with no value yields None,
|
||||
# ``description: 2`` an int, an unquoted ``true`` a bool.
|
||||
# ``_render_basic_toml_string`` iterates the value and calls ord()
|
||||
# on each character, so a non-string raises a raw TypeError -- and a
|
||||
# list of single-character items is silently concatenated into a
|
||||
# wrong value (``["a", "b"]`` -> ``"ab"``). Coerce first, matching
|
||||
# ``render_yaml_command`` below and ``TomlIntegration
|
||||
# ._extract_description``, which both normalise it already.
|
||||
description = frontmatter["description"]
|
||||
if not isinstance(description, str):
|
||||
description = str(description) if description is not None else ""
|
||||
toml_lines.append(
|
||||
f"description = {self._render_basic_toml_string(frontmatter['description'])}"
|
||||
f"description = {self._render_basic_toml_string(description)}"
|
||||
)
|
||||
toml_lines.append("")
|
||||
|
||||
|
||||
@@ -2720,6 +2720,36 @@ Real body starts here.
|
||||
|
||||
assert parsed["description"] == "first line\nsecond line\n"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("description", "expected"),
|
||||
[
|
||||
(None, ""), # "description:" with no value
|
||||
(42, "42"), # unquoted number
|
||||
(True, "True"), # unquoted boolean
|
||||
(["a", "b"], "['a', 'b']"), # was silently concatenated to "ab"
|
||||
],
|
||||
)
|
||||
def test_render_toml_command_coerces_non_string_description(
|
||||
self, description, expected
|
||||
):
|
||||
"""Frontmatter comes from yaml.safe_load, so description can be any type.
|
||||
|
||||
_render_basic_toml_string iterates the value and calls ord() per
|
||||
character, so a non-string raised a raw TypeError and a list of
|
||||
single-character items was silently concatenated into a wrong value.
|
||||
render_yaml_command (same class) already coerces; this brings the TOML
|
||||
branch to parity.
|
||||
"""
|
||||
from specify_cli.agents import CommandRegistrar as AgentCommandRegistrar
|
||||
|
||||
registrar = AgentCommandRegistrar()
|
||||
output = registrar.render_toml_command(
|
||||
{"description": description}, "body", "extension:test-ext"
|
||||
)
|
||||
|
||||
parsed = tomllib.loads(output)
|
||||
assert parsed["description"] == expected
|
||||
|
||||
def test_render_toml_command_escapes_control_characters(self):
|
||||
"""Control characters and a lone CR must be escaped so the TOML parses.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user