From efd79299abf8f08c4d1b5ce8b6200f41626d5ba5 Mon Sep 17 00:00:00 2001 From: jawwad-ali Date: Wed, 24 Jun 2026 02:34:43 +0500 Subject: [PATCH] fix: render valid TOML when a command body contains backslashes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit render_toml_command() emitted the body inside a multiline *basic* TOML string ("""..."""), which processes backslash escape sequences. A command body containing a backslash — e.g. a Windows path like C:\Users\... whose \U reads as an invalid unicode escape — therefore produced unparseable TOML ("Invalid hex value"), so the generated Gemini/Tabnine command file failed to load. A body ending in a backslash also silently ate the closing newline via TOML line-continuation. Route bodies containing a backslash to the multiline *literal* form ('''...'''), which does not process escapes, or to the escaped basic string when both triple-quote styles are present. Mirrors the escaping already done by base.py's TomlIntegration. Add tests covering a Windows path, a trailing backslash, and the backslash + both-triple-quote-styles fallback. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/specify_cli/agents.py | 11 ++++++++--- tests/test_extensions.py | 41 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/specify_cli/agents.py b/src/specify_cli/agents.py index 28dc8037e7..da3ca49fa6 100644 --- a/src/specify_cli/agents.py +++ b/src/specify_cli/agents.py @@ -236,9 +236,14 @@ def render_toml_command(self, frontmatter: dict, body: str, source_id: str) -> s toml_lines.append(f"# Source: {source_id}") toml_lines.append("") - # Keep TOML output valid even when body contains triple-quote delimiters. - # Prefer multiline forms, then fall back to escaped basic string. - if '"""' not in body: + # Keep TOML output valid even when body contains triple-quote delimiters + # or backslashes. Prefer multiline forms, then fall back to escaped basic + # string. A multiline *basic* string ("""...""") processes backslash escape + # sequences, so a body containing a backslash (e.g. a Windows path + # ``C:\\Users\\...`` whose ``\\U`` reads as an invalid unicode escape) would + # produce unparseable TOML — route those to the *literal* form ('''...'''), + # which does not process escapes, or to the escaped basic string. + if '"""' not in body and "\\" not in body: toml_lines.append('prompt = """') toml_lines.append(body) toml_lines.append('"""') diff --git a/tests/test_extensions.py b/tests/test_extensions.py index 4cd052fd81..df32e7ecb3 100644 --- a/tests/test_extensions.py +++ b/tests/test_extensions.py @@ -1669,6 +1669,47 @@ def test_render_toml_command_preserves_multiline_description(self): assert parsed["description"] == "first line\nsecond line\n" + def test_render_toml_command_preserves_backslashes_in_body(self): + """A backslash in the body (e.g. a Windows path) must not break TOML. + + A multiline basic string ("\"\"\"") processes backslash escapes, so + ``C:\\Users`` (``\\U``) would render as invalid TOML; the body must + round-trip with backslashes intact. + """ + from specify_cli.agents import CommandRegistrar as AgentCommandRegistrar + + registrar = AgentCommandRegistrar() + output = registrar.render_toml_command( + {"description": "x"}, + r"Run C:\Users\dev\tool.exe then report.", + "extension:test-ext", + ) + parsed = tomllib.loads(output) # must not raise + assert parsed["prompt"].strip() == r"Run C:\Users\dev\tool.exe then report." + + def test_render_toml_command_handles_trailing_backslash(self): + """A body ending in a backslash must round-trip without corruption.""" + from specify_cli.agents import CommandRegistrar as AgentCommandRegistrar + + registrar = AgentCommandRegistrar() + output = registrar.render_toml_command( + {"description": "x"}, + "path ends with sep\\", + "extension:test-ext", + ) + parsed = tomllib.loads(output) + assert parsed["prompt"].strip() == "path ends with sep\\" + + def test_render_toml_command_backslash_with_both_triple_quotes_escapes(self): + """Body with a backslash and both triple-quote styles → escaped basic string.""" + from specify_cli.agents import CommandRegistrar as AgentCommandRegistrar + + registrar = AgentCommandRegistrar() + body = "a \\ b\nc \"\"\" d\ne ''' f" + output = registrar.render_toml_command({"description": "x"}, body, "extension:test-ext") + parsed = tomllib.loads(output) + assert parsed["prompt"] == body + def test_register_commands_for_claude(self, extension_dir, project_dir): """Test registering commands for Claude agent.""" # Create .claude directory