Skip to content

fix(integrations): escape control characters in goose recipe YAML renderer#3384

Open
marcelsafin wants to merge 2 commits into
github:mainfrom
marcelsafin:fix/3382-yaml-control-chars
Open

fix(integrations): escape control characters in goose recipe YAML renderer#3384
marcelsafin wants to merge 2 commits into
github:mainfrom
marcelsafin:fix/3382-yaml-control-chars

Conversation

@marcelsafin

Copy link
Copy Markdown
Contributor

Description

Fixes #3382

YamlIntegration._render_yaml writes the command body verbatim into a |2 literal 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, \x7F and 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

  • Tested locally with uv run specify --help
  • Ran existing tests with uv sync && uv run pytest — 3776 passed, 105 skipped
  • Tested with a sample project (if applicable)

Two regression tests in the shared YamlIntegrationTests mixin (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 on main.

AI Disclosure

  • I did not use AI assistance for this contribution
  • I did use AI assistance (describe below)

Bug found, patch and tests written with GitHub Copilot CLI; reproduction and full test suite run and verified locally by me.

…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>
@marcelsafin marcelsafin requested a review from mnriem as a code owner July 6, 2026 23:17
Copilot AI review requested due to automatic review settings July 6, 2026 23:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 prompt as an escaped double-quoted scalar via yaml.safe_dump instead of a |2 literal block scalar.
  • Add regression tests ensuring both control characters and a bare \r round-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.

@mnriem mnriem requested a review from Copilot July 7, 2026 18:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Low

Comment on lines +1191 to +1193
prompt_yaml = yaml.safe_dump(
{"prompt": body}, allow_unicode=True, default_style='"', width=float("inf")
).strip()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: generated goose recipe YAML fails to parse when the command body contains control characters or a bare CR

3 participants