fix(integrations): escape control characters in goose recipe YAML renderer#3384
Open
marcelsafin wants to merge 2 commits into
Open
fix(integrations): escape control characters in goose recipe YAML renderer#3384marcelsafin wants to merge 2 commits into
marcelsafin wants to merge 2 commits into
Conversation
…derer YAML forbids C0 control characters (except tab and newline) and DEL in every scalar form, and a bare CR acts as a line break inside a block scalar. _render_yaml wrote the body verbatim into a |2 literal block scalar, so such bodies produced recipes the YAML parser rejects. Detect block-scalar-unsafe characters and fall back to an escaped double-quoted scalar via yaml.safe_dump, mirroring the TOML renderer's fallback strategy from github#3341. Fixes github#3382 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes Goose recipe YAML generation in YamlIntegration._render_yaml so command bodies containing C0 control characters (excluding tab/LF), DEL, or a bare carriage return no longer produce YAML that yaml.safe_load rejects.
Changes:
- Add a precompiled control-character detector and, when triggered, render
promptas an escaped double-quoted scalar viayaml.safe_dumpinstead of a|2literal block scalar. - Add regression tests ensuring both control characters and a bare
\rround-trip through YAML parsing.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
src/specify_cli/integrations/base.py |
Adds block-scalar “unsafe” detection and a safe_dump-based quoted-scalar fallback for prompt to keep generated Goose recipes parseable. |
tests/integrations/test_integration_base_yaml.py |
Adds regression coverage to ensure YAML rendering remains parseable and the prompt round-trips for control chars and bare CR. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+1191
to
+1193
| prompt_yaml = yaml.safe_dump( | ||
| {"prompt": body}, allow_unicode=True, default_style='"', width=float("inf") | ||
| ).strip() |
Contributor
Author
There was a problem hiding this comment.
Switched to sys.maxsize in ff0afae. For reference, PyYAML 6.0.3 accepts float("inf") (the emitter only compares against best_width) and produces byte-identical output, but an int is the documented type and avoids the question entirely.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Fixes #3382
YamlIntegration._render_yamlwrites the command body verbatim into a|2literal block scalar. YAML forbids C0 control characters (except tab and newline) and DEL in every scalar form, and a bare CR acts as a line break inside a block scalar, so a body containing any of these produced a goose recipe the YAML parser rejects (unacceptable character #x0008: special characters are not allowed).Root cause: the block-scalar path has no escaping mechanism at all; there is no string form in YAML that carries these bytes raw.
Fix: detect block-scalar-unsafe characters with a precompiled character class and fall back to an escaped double-quoted scalar rendered by
yaml.safe_dump, which escapes them as\b,\r,\x7Fand so on. Bodies without such characters keep the existing readable block-scalar output byte for byte. This mirrors the fallback strategy #3341 gives the TOML renderers for the same bug class (#3340).Testing
uv run specify --helpuv sync && uv run pytest— 3776 passed, 105 skippedTwo regression tests in the shared
YamlIntegrationTestsmixin (exercised via the goose integration): control characters (\x08,\x0c,\x1b,\x7f) and a bare CR, both asserting the rendered recipe parses and the prompt round-trips. Both fail onmain.AI Disclosure
Bug found, patch and tests written with GitHub Copilot CLI; reproduction and full test suite run and verified locally by me.