Skip to content

fix: use double quotes in skill templates to prevent YAML parse errors#1903

Open
nikolasdehor wants to merge 1 commit intobmad-code-org:mainfrom
nikolasdehor:fix/template-description-quoting
Open

fix: use double quotes in skill templates to prevent YAML parse errors#1903
nikolasdehor wants to merge 1 commit intobmad-code-org:mainfrom
nikolasdehor:fix/template-description-quoting

Conversation

@nikolasdehor
Copy link
Copy Markdown
Contributor

What

Switch default skill templates from single-quoted to double-quoted YAML values and escape descriptions during rendering.

Why

The getDefaultTemplate() method in _config-driven.js wraps {{description}} in single quotes. When a workflow description contains an apostrophe (e.g., "agent's workflow"), the generated YAML frontmatter becomes invalid, causing parse failures in IDEs like Antigravity. This is the same class of issue fixed for Gemini templates in #1746.

Fixes #1744

How

  • Changed name: '{{name}}'name: "{{name}}" in both agent and default templates
  • Changed description: '{{description}}'description: "{{description}}" in both templates
  • Added replaceAll('"', String.raw\\"`)to description escaping inrenderTemplate()` for safety

Testing

  • Full test suite passes (215 installation tests, 52 schema tests, lint, format)
  • Verified the change applies to the fallback templates used by platforms without custom templates

Default skill templates used single-quoted YAML values for name
and description fields. When descriptions contain apostrophes
(e.g., "agent's workflow"), the generated YAML frontmatter would
be invalid, causing parse failures in IDEs like Antigravity.

Switches templates to double quotes and escapes any double quotes
in the description during template rendering.

Fixes bmad-code-org#1744
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 11, 2026

📝 Walkthrough

Walkthrough

The pull request updates the Antigravity template generator to use double quotes for YAML frontmatter placeholders and escapes double quotes within the description field to prevent YAML parsing errors when descriptions contain single quotes.

Changes

Cohort / File(s) Summary
YAML Frontmatter Template Quoting
tools/cli/installers/lib/ide/_config-driven.js
Changed YAML frontmatter placeholders from single-quoted to double-quoted syntax for name and description fields in agent and general templates. Added double-quote escaping within the {{description}} placeholder to prevent string parsing issues when descriptions contain single quotes.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Possibly related PRs

  • PR #1501: Also modifies tools/cli/installers/lib/ide/_config-driven.js to update YAML frontmatter handling in templates, addressing related escaping and quoting concerns for generated workflow configurations.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: switching to double quotes in skill templates to fix YAML parse errors caused by apostrophes in descriptions.
Description check ✅ Passed The description clearly explains the what, why, and how of the changes, directly addressing the YAML parsing issue with apostrophes in workflow descriptions and referencing the related issue #1744.
Linked Issues check ✅ Passed The PR fully addresses issue #1744 by changing templates from single-quoted to double-quoted YAML values and escaping descriptions, directly fixing the YAML parse errors caused by unescaped apostrophes in workflow descriptions.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the YAML parsing issue: template quote style updates and description escaping in _config-driven.js. No unrelated modifications are present.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
tools/cli/installers/lib/ide/_config-driven.js (3)

467-472: ⚠️ Potential issue | 🟡 Minor

Please add regression cases beyond apostrophes.

This needs explicit coverage for embedded ", backslashes, : , #, and multiline descriptions. Those are the sequences most likely to break or silently truncate Antigravity frontmatter.

Based on learnings: : and # are confirmed parser traps unless the description is safely quoted.


467-472: ⚠️ Potential issue | 🟠 Major

Unescaped descriptions in sibling YAML generators leave parse failures open.

agent-command-generator.js:80 and compiler.js:27 both interpolate descriptions directly into YAML frontmatter without escaping double quotes or other special characters. These match the vulnerability fixed in _config-driven.js:471 and must be hardened using the same escaping logic.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/cli/installers/lib/ide/_config-driven.js` around lines 467 - 472, The
YAML frontmatter in agent-command-generator.js (near the description
interpolation at/around line 80) and in compiler.js (around line 27) currently
injects artifact/command descriptions unescaped; update those interpolation
sites to escape double quotes the same way as in _config-driven.js (i.e.,
replace occurrences of " in the description with \" before inserting into YAML)
so that descriptions are safe for YAML parsing—apply the same replaceAll('"',
String.raw`\"`) (or a shared helper used by _config-driven.js) to the
description value before interpolation.

467-472: ⚠️ Potential issue | 🟠 Major

Escaping only " is insufficient for YAML double-quoted scalars in renderTemplate().

Backslashes, newlines, and tabs are still emitted unescaped, causing YAML to misinterpret them. For example, descriptions containing Windows paths like C:\new\tools will be corrupted when parsed back from YAML.

In YAML double-quoted strings, the sequence \ must be escaped as \\, actual newlines must be escaped as \\n, and tabs as \\t. The current code on line 471 only escapes ", leaving these characters to be interpreted as YAML escape sequences.

Add proper escaping to handle backslashes, newlines, tabs, and carriage returns in addition to quotes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/cli/installers/lib/ide/_config-driven.js` around lines 467 - 472, The
description replacement in renderTemplate currently only escapes double quotes,
which breaks YAML double-quoted scalars; update the code that builds the
description replacement (the expression using artifact.description in the render
block) to first escape backslashes, carriage returns, newlines and tabs (\\ ->
\\\\, \r -> \\r, \n -> \\n, \t -> \\t) and then escape quotes (\"), so that the
final substituted string is safe for YAML double-quoted scalars; locate the
renderTemplate / the variable `rendered` where `.replaceAll('{{description}}',
(artifact.description ...` is performed and apply these additional replaceAll
steps to the description value before substitution.
🧹 Nitpick comments (5)
tools/cli/installers/lib/ide/_config-driven.js (5)

467-472: Validate rendered frontmatter before writing it.

Right now flat-file installs trust raw string substitution. A cheap parse round-trip in tests—or a guard around generated frontmatter in this path—would catch invalid YAML before it ships again.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/cli/installers/lib/ide/_config-driven.js` around lines 467 - 472, After
performing the template substitutions into the rendered string (the variable
rendered which replaces
'{{name}}','{{module}}','{{path}}','{{description}}','{{workflow_path}}'),
validate the generated frontmatter before writing: extract the YAML frontmatter
block (the leading --- ... ---), parse it with a YAML parser (e.g.,
js-yaml/YAML.parse) to ensure it round-trips or is syntactically valid, and fail
loudly (throw or log and skip write) if parsing fails; add a unit/integration
test that runs the same substitution and asserts the frontmatter parses
successfully to guard future regressions.

467-472: Extract a named YAML-scalar escape helper.

Burying the policy inside one chained .replaceAll() is how this kind of bug reappears in the next emitter. A dedicated helper makes the contract obvious and reusable for both name and description.

Based on learnings: a future PR should add an escapeYamlDoubleQuote helper in tools/cli/installers/lib/ide/_config-driven.js.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/cli/installers/lib/ide/_config-driven.js` around lines 467 - 472,
Extract a small helper named escapeYamlDoubleQuote that takes a string and
returns it with double quotes escaped (e.g. replacing " with \" ), then replace
the inline escaping logic in the replacement chain by calling that helper for
both artifact.name and artifact.description; update the chain that currently
mutates rendered (the .replaceAll('{{name}}', artifact.name || '') and
.replaceAll('{{description}}', (artifact.description || `${artifact.name}
${artifact.type || ''}`).replaceAll('"', String.raw`\"`)) to call
escapeYamlDoubleQuote(artifact.name || '') and
escapeYamlDoubleQuote(artifact.description || `${artifact.name} ${artifact.type
|| ''}`) respectively so the escaping policy is centralized and reusable.

403-429: Stop hand-writing YAML frontmatter here.

This string template now depends on every future edit remembering YAML quoting rules for name, description, and any new keys. Serialize the frontmatter object with yaml.stringify and keep only the body as literal template text.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/cli/installers/lib/ide/_config-driven.js` around lines 403 - 429,
Replace the hand-written YAML frontmatter in getDefaultTemplate with a
serialized object using yaml.stringify: build a frontmatter object (include keys
like name, description and conditionally disable-model-invocation when
artifactType === 'agent'), call yaml.stringify(frontmatter), then concatenate
that string with the remaining literal body text (the activation block for
'agent' and the LOAD/execute line for defaults). Import/require the yaml module
(e.g., yaml.stringify) at the top, and ensure the template uses placeholders
({{name}}, {{description}}, {{bmadFolderName}}, {{path}}) only in the body
portion while the frontmatter is produced from the object so future keys are
safely quoted.

467-472: The escaping policy is already inconsistent across generators.

tools/cli/installers/lib/ide/shared/task-tool-command-generator.js:175-182 still uses single-quote doubling, while this path now uses backslash escapes. Different emitters producing the same frontmatter field with different rules is brittle; centralize it by output format instead of per file.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/cli/installers/lib/ide/_config-driven.js` around lines 467 - 472, The
frontmatter escaping is inconsistent: replace the per-file ad hoc escaping in
_config-driven.js (the rendered.replaceAll chain using backslash escapes and
variables rendered, artifact, pathToUse) by calling a shared utility like
escapeFrontmatterValue(format, value) that encodes quotes according to the
output format; implement this helper (e.g., in a new/common utils module) and
update tools/cli/installers/lib/ide/_config-driven.js to use it for name,
module, path, description, workflow_path and also change
tools/cli/installers/lib/ide/shared/task-tool-command-generator.js to use the
same helper so both generators use the same escape rules.

467-472: YAML escaping does not belong in a format-agnostic renderer.

renderTemplate() feeds .md, .toml, .yaml, and .yml templates. Hardcoding YAML-style backslashes into {{description}} means any non-YAML or non-frontmatter use of that placeholder will get corrupted output.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/cli/installers/lib/ide/_config-driven.js` around lines 467 - 472, The
renderer is applying YAML-specific escaping to the description placeholder (the
.replaceAll('"', String.raw`\"`) in the renderTemplate output for
'{{description}}'), which corrupts non-YAML outputs; change renderTemplate (or
the code path that builds rendered) so it does NOT hardcode YAML escapes into
artifact.description — either remove the .replaceAll call for '{{description}}'
and leave raw text, or add an explicit format flag/parameter (e.g., format or
fileExt) and only apply the escape when that flag indicates YAML/YML (and apply
the appropriate escaping function there); target the replacement that uses
'{{description}}' and artifact.description in _config-driven.js.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tools/cli/installers/lib/ide/_config-driven.js`:
- Around line 406-407: The YAML frontmatter uses name: "{{name}}" without
escaping, so update the code path that calls renderTemplate() (the place that
injects artifact.name) to escape double-quotes and backslashes the same way
description is escaped: replace " with \" and \ with \\ before passing
artifact.name into the template. Locate the renderTemplate() call or the
variable that supplies artifact.name in _config-driven.js and apply the same
escaping function used for description (or add a small helper) so names like
C:\tools or Bob "QA" produce valid YAML.

---

Outside diff comments:
In `@tools/cli/installers/lib/ide/_config-driven.js`:
- Around line 467-472: The YAML frontmatter in agent-command-generator.js (near
the description interpolation at/around line 80) and in compiler.js (around line
27) currently injects artifact/command descriptions unescaped; update those
interpolation sites to escape double quotes the same way as in _config-driven.js
(i.e., replace occurrences of " in the description with \" before inserting into
YAML) so that descriptions are safe for YAML parsing—apply the same
replaceAll('"', String.raw`\"`) (or a shared helper used by _config-driven.js)
to the description value before interpolation.
- Around line 467-472: The description replacement in renderTemplate currently
only escapes double quotes, which breaks YAML double-quoted scalars; update the
code that builds the description replacement (the expression using
artifact.description in the render block) to first escape backslashes, carriage
returns, newlines and tabs (\\ -> \\\\, \r -> \\r, \n -> \\n, \t -> \\t) and
then escape quotes (\"), so that the final substituted string is safe for YAML
double-quoted scalars; locate the renderTemplate / the variable `rendered` where
`.replaceAll('{{description}}', (artifact.description ...` is performed and
apply these additional replaceAll steps to the description value before
substitution.

---

Nitpick comments:
In `@tools/cli/installers/lib/ide/_config-driven.js`:
- Around line 467-472: After performing the template substitutions into the
rendered string (the variable rendered which replaces
'{{name}}','{{module}}','{{path}}','{{description}}','{{workflow_path}}'),
validate the generated frontmatter before writing: extract the YAML frontmatter
block (the leading --- ... ---), parse it with a YAML parser (e.g.,
js-yaml/YAML.parse) to ensure it round-trips or is syntactically valid, and fail
loudly (throw or log and skip write) if parsing fails; add a unit/integration
test that runs the same substitution and asserts the frontmatter parses
successfully to guard future regressions.
- Around line 467-472: Extract a small helper named escapeYamlDoubleQuote that
takes a string and returns it with double quotes escaped (e.g. replacing " with
\" ), then replace the inline escaping logic in the replacement chain by calling
that helper for both artifact.name and artifact.description; update the chain
that currently mutates rendered (the .replaceAll('{{name}}', artifact.name ||
'') and .replaceAll('{{description}}', (artifact.description ||
`${artifact.name} ${artifact.type || ''}`).replaceAll('"', String.raw`\"`)) to
call escapeYamlDoubleQuote(artifact.name || '') and
escapeYamlDoubleQuote(artifact.description || `${artifact.name} ${artifact.type
|| ''}`) respectively so the escaping policy is centralized and reusable.
- Around line 403-429: Replace the hand-written YAML frontmatter in
getDefaultTemplate with a serialized object using yaml.stringify: build a
frontmatter object (include keys like name, description and conditionally
disable-model-invocation when artifactType === 'agent'), call
yaml.stringify(frontmatter), then concatenate that string with the remaining
literal body text (the activation block for 'agent' and the LOAD/execute line
for defaults). Import/require the yaml module (e.g., yaml.stringify) at the top,
and ensure the template uses placeholders ({{name}}, {{description}},
{{bmadFolderName}}, {{path}}) only in the body portion while the frontmatter is
produced from the object so future keys are safely quoted.
- Around line 467-472: The frontmatter escaping is inconsistent: replace the
per-file ad hoc escaping in _config-driven.js (the rendered.replaceAll chain
using backslash escapes and variables rendered, artifact, pathToUse) by calling
a shared utility like escapeFrontmatterValue(format, value) that encodes quotes
according to the output format; implement this helper (e.g., in a new/common
utils module) and update tools/cli/installers/lib/ide/_config-driven.js to use
it for name, module, path, description, workflow_path and also change
tools/cli/installers/lib/ide/shared/task-tool-command-generator.js to use the
same helper so both generators use the same escape rules.
- Around line 467-472: The renderer is applying YAML-specific escaping to the
description placeholder (the .replaceAll('"', String.raw`\"`) in the
renderTemplate output for '{{description}}'), which corrupts non-YAML outputs;
change renderTemplate (or the code path that builds rendered) so it does NOT
hardcode YAML escapes into artifact.description — either remove the .replaceAll
call for '{{description}}' and leave raw text, or add an explicit format
flag/parameter (e.g., format or fileExt) and only apply the escape when that
flag indicates YAML/YML (and apply the appropriate escaping function there);
target the replacement that uses '{{description}}' and artifact.description in
_config-driven.js.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 72cc5d7a-53dd-4287-8546-e17db0e5f5c1

📥 Commits

Reviewing files that changed from the base of the PR and between 32693f1 and b696e9a.

📒 Files selected for processing (1)
  • tools/cli/installers/lib/ide/_config-driven.js

Comment on lines +406 to +407
name: "{{name}}"
description: "{{description}}"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the relevant lines in _config-driven.js
head -n 480 tools/cli/installers/lib/ide/_config-driven.js | tail -n 85

Repository: bmad-code-org/BMAD-METHOD

Length of output: 2675


🏁 Script executed:

# Let's also check what the renderTemplate method does with name and description
rg -A 20 "renderTemplate.*{" tools/cli/installers/lib/ide/_config-driven.js | head -n 50

Repository: bmad-code-org/BMAD-METHOD

Length of output: 166


🏁 Script executed:

# Let's search for how name is used throughout the file
rg "artifact\.name|{{name}}" tools/cli/installers/lib/ide/_config-driven.js -B 2 -A 2

Repository: bmad-code-org/BMAD-METHOD

Length of output: 182


🏁 Script executed:

# Check if there are similar escaping patterns in related generator files mentioned in learnings
rg "description.*replace|escapeYaml" tools/cli/installers/lib/ide/ -r --type js

Repository: bmad-code-org/BMAD-METHOD

Length of output: 318


🏁 Script executed:

# Verify if name can contain special characters and trace how it's populated
rg -B 5 "artifact\.name\s*=" tools/cli/installers/lib/ide/_config-driven.js | head -n 40

Repository: bmad-code-org/BMAD-METHOD

Length of output: 51


🏁 Script executed:

# Check constructor and parameter sources to understand if name is user-controlled
rg "new\s+ConfigDrivenIdeSetup|\.name\s*=" tools/cli/installers/lib/ide/ -A 2 | head -n 50

Repository: bmad-code-org/BMAD-METHOD

Length of output: 1721


🏁 Script executed:

# Verify the escaping is indeed incomplete by checking what the current description escaping handles
cat tools/cli/installers/lib/ide/_config-driven.js | grep -A 1 "description.*replaceAll"

Repository: bmad-code-org/BMAD-METHOD

Length of output: 257


Escape {{name}} in double-quoted YAML frontmatter.

renderTemplate() injects artifact.name without escaping while the default templates now wrap it in double quotes (lines 406–407, 421–422). A name containing " or \ (e.g., C:\tools or Bob "QA") breaks YAML parsing and prevents the command/workflow/task/agent from loading.

Apply the same escaping logic as {{description}} (at minimum, replace " with \" and \ with \\ per YAML double-quote rules).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/cli/installers/lib/ide/_config-driven.js` around lines 406 - 407, The
YAML frontmatter uses name: "{{name}}" without escaping, so update the code path
that calls renderTemplate() (the place that injects artifact.name) to escape
double-quotes and backslashes the same way description is escaped: replace "
with \" and \ with \\ before passing artifact.name into the template. Locate the
renderTemplate() call or the variable that supplies artifact.name in
_config-driven.js and apply the same escaping function used for description (or
add a small helper) so names like C:\tools or Bob "QA" produce valid YAML.

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] v6.0.2 Antigravity workflow descriptions have unescaped single quotes, breaking YAML frontmatter parsing

1 participant