Conversation
render_template.cjs and interpolate_prompt.cjs were processing
{{#if}} markers inside fenced code blocks, corrupting JavaScript
string literals in workflow prompts (e.g. test harness scripts
with Handlebars-style strings as test inputs).
Fix: extract fenced code blocks with NUL-byte placeholders before
processing {{#if}} conditionals, then restore them afterward.
Discovered via run 24029642787 (Daily Go Function Namer, claude engine).
The daily-rendering-scripts-verifier workflow prompt had its Phase 5
test case inputs corrupted by the template renderer.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
…ed code block fix Agent-Logs-Url: https://github.com/github/gh-aw/sessions/3a3aaffd-3a87-4351-b637-998d24056292 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Done in commit
|
There was a problem hiding this comment.
Pull request overview
Fixes prompt/template rendering so {{#if ...}} conditionals are not processed inside fenced code blocks, preventing corruption of code samples / string literals embedded in workflow prompts.
Changes:
- Add fenced-code-block “strip + placeholder + restore” flow around conditional rendering in both
render_template.cjsandinterpolate_prompt.cjs. - Add regression tests ensuring
{{#if}}markers inside fenced blocks are preserved, while conditionals outside are still rendered. - Update test stubs to include
core.warningfor the new runtime fence-count assertion.
Show a summary per file
| File | Description |
|---|---|
| actions/setup/js/render_template.cjs | Preserves fenced blocks during conditional processing; restores afterward; warns on fence-count mismatch. |
| actions/setup/js/render_template.test.cjs | Adds fenced-code-block regression tests for renderMarkdownTemplate. |
| actions/setup/js/interpolate_prompt.cjs | Applies the same fenced-block preservation approach in the prompt interpolator. |
| actions/setup/js/interpolate_prompt.test.cjs | Updates core mock to include warning. |
| actions/setup/js/interpolate_prompt_additional.test.cjs | Updates core mock to include warning and adds fenced-block regression tests. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comments suppressed due to low confidence (1)
actions/setup/js/render_template.cjs:33
- Current fenced-block regex only matches fences starting immediately with backticks; valid Markdown allows up to 3 leading spaces before a fenced code block. Indented fences (e.g. " ```") would not be preserved and {{#if}} markers inside them would still be processed. Consider allowing optional leading whitespace and requiring the closing fence to match the same indentation level.
// Preserve fenced code blocks to avoid processing {{#if}} markers inside them
const _codeBlocks = [];
const _FENCE_PH = "\x00FENCE\x00";
const _stripped = markdown.replace(/`{3,}[^\n]*\n[\s\S]*?\n`{3,}[ \t]*/g, m => {
_codeBlocks.push(m);
return `${_FENCE_PH}${_codeBlocks.length - 1}${_FENCE_PH}`;
});
- Files reviewed: 5/5 changed files
- Comments generated: 4
| // Preserve fenced code blocks to avoid processing {{#if}} markers inside them | ||
| const _codeBlocks = []; | ||
| const _FENCE_PH = "\x00FENCE\x00"; | ||
| const _stripped = markdown.replace(/`{3,}[^\n]*\n[\s\S]*?\n`{3,}[ \t]*/g, m => { | ||
| _codeBlocks.push(m); | ||
| return `${_FENCE_PH}${_codeBlocks.length - 1}${_FENCE_PH}`; |
There was a problem hiding this comment.
The fenced-code-block extraction regex can break for fences longer than 3 backticks (e.g. …), because it will treat an inner closing ``` line as the end of the outer block. This can reintroduce corruption for prompts that wrap markdown in 4–6 backticks to embed nested triple-backtick blocks. Consider capturing the opening fence (and any leading indentation) and restoring only when you see a closing fence of the same length at the start of a line (e.g. via a multiline regex with a backreference).
This issue also appears on line 27 of the same file.
| // Preserve fenced code blocks to avoid processing {{#if}} markers inside them | |
| const _codeBlocks = []; | |
| const _FENCE_PH = "\x00FENCE\x00"; | |
| const _stripped = markdown.replace(/`{3,}[^\n]*\n[\s\S]*?\n`{3,}[ \t]*/g, m => { | |
| _codeBlocks.push(m); | |
| return `${_FENCE_PH}${_codeBlocks.length - 1}${_FENCE_PH}`; | |
| // Preserve fenced code blocks to avoid processing {{#if}} markers inside them. | |
| // Match only a closing fence with the same indentation and backtick length as the opener, | |
| // so outer 4+ backtick fences are not terminated by inner triple-backtick lines. | |
| const _codeBlocks = []; | |
| const _FENCE_PH = "\x00FENCE\x00"; | |
| const _FENCED_CODE_BLOCK_RE = /(^|\n)([ \t]*)(`{3,})[^\n]*\n[\s\S]*?\n\2\3[ \t]*(?=\n|$)/g; | |
| const _stripped = markdown.replace(_FENCED_CODE_BLOCK_RE, (m, lineStart) => { | |
| const block = m.slice(lineStart.length); | |
| _codeBlocks.push(block); | |
| return `${lineStart}${_FENCE_PH}${_codeBlocks.length - 1}${_FENCE_PH}`; |
| // Preserve fenced code blocks to avoid processing {{#if}} markers inside them | ||
| const _codeBlocks = []; | ||
| const _FENCE_PH = "\x00FENCE\x00"; | ||
| const _stripped = markdown.replace(/`{3,}[^\n]*\n[\s\S]*?\n`{3,}[ \t]*/g, m => { |
There was a problem hiding this comment.
Same issue as render_template.cjs: the fenced-code-block extraction regex can terminate an outer 4–6 backtick fence at an inner closing ``` line, which is common when embedding Markdown that itself contains triple-backtick code blocks. Use an opening-fence capture + backreference (and multiline line-start anchoring) so only a matching-length closing fence ends the preserved block.
| // Preserve fenced code blocks to avoid processing {{#if}} markers inside them | |
| const _codeBlocks = []; | |
| const _FENCE_PH = "\x00FENCE\x00"; | |
| const _stripped = markdown.replace(/`{3,}[^\n]*\n[\s\S]*?\n`{3,}[ \t]*/g, m => { | |
| // Preserve fenced code blocks to avoid processing {{#if}} markers inside them. | |
| // Capture the opening fence and require the closing fence to use the same | |
| // number of backticks, anchored at the start of a line, so outer 4+ backtick | |
| // fences are not terminated by inner triple-backtick lines. | |
| const _codeBlocks = []; | |
| const _FENCE_PH = "\x00FENCE\x00"; | |
| const _stripped = markdown.replace(/^(`{3,})[^\n]*\n[\s\S]*?^\1[ \t]*$/gm, m => { |
| const input = "{{#if true}}\nKeep\n{{/if}}\n```python\nprint('hello')\n```"; | ||
| const output = renderMarkdownTemplate(input); | ||
| expect(output).toBe("Keep\n```python\nprint('hello')\n```"); | ||
| }); |
There was a problem hiding this comment.
Test coverage: these fenced-code-block tests cover only non-indented triple-backtick fences. Adding regression cases for (1) indented fences (up to 3 leading spaces) and (2) outer fences of 4–6 backticks containing an inner ``` block would help prevent reintroducing corruption with nested fenced blocks.
| }); | |
| }); | |
| it("should preserve indented fenced code blocks unchanged", () => { | |
| const input = " ```js\n{{#if false}}\nHidden\n{{/if}}\n ```"; | |
| const output = renderMarkdownTemplate(input); | |
| expect(output).toBe(input); | |
| }); | |
| it("should process conditionals outside indented fenced blocks while preserving inside", () => { | |
| const input = "{{#if false}}\nRemove this\n{{/if}}\n ```js\n{{#if false}}\nKeep this\n{{/if}}\n ```"; | |
| const output = renderMarkdownTemplate(input); | |
| expect(output).toBe(" ```js\n{{#if false}}\nKeep this\n{{/if}}\n ```"); | |
| }); | |
| it("should preserve outer fences of 4-6 backticks containing an inner triple-backtick block", () => { | |
| const input = "````markdown\n```js\n{{#if false}}\nHidden\n{{/if}}\n```\n````"; | |
| const output = renderMarkdownTemplate(input); | |
| expect(output).toBe(input); | |
| }); | |
| it("should preserve fence count for nested fenced blocks with longer outer fences", () => { | |
| const input = "`````\n```js\n{{#if true}}\nVisible\n{{/if}}\n```\n`````"; | |
| const output = renderMarkdownTemplate(input); | |
| expect((output.match(/`{3,}/g) || []).length).toBe((input.match(/`{3,}/g) || []).length); | |
| expect(output).toBe(input); | |
| }); |
| it("should handle fenced blocks adjacent to conditionals", () => { | ||
| const input = "{{#if true}}\nKeep\n{{/if}}\n```python\nprint('hello')\n```"; | ||
| const output = renderMarkdownTemplate(input); | ||
| expect(output).toBe("Keep\n```python\nprint('hello')\n```"); |
There was a problem hiding this comment.
Test coverage: consider adding fenced-code-block cases for indented fences and for an outer 4–6 backtick fence containing an inner triple-backtick block. The current preservation regex can mis-handle nested fences, and a focused regression test here would catch it.
| expect(output).toBe("Keep\n```python\nprint('hello')\n```"); | |
| expect(output).toBe("Keep\n```python\nprint('hello')\n```"); | |
| }), | |
| it("should preserve indented fenced code blocks unchanged", () => { | |
| const input = " ```js\n {{#if false}}\n Hidden\n {{/if}}\n ```"; | |
| const output = renderMarkdownTemplate(input); | |
| expect(output).toBe(input); | |
| }), | |
| it("should preserve an outer 4-backtick fence containing an inner triple-backtick block", () => { | |
| const input = "````md\n```js\n{{#if false}}\nHidden\n{{/if}}\n```\n````"; | |
| const output = renderMarkdownTemplate(input); | |
| expect(output).toBe(input); |
What
render_template.cjsandinterpolate_prompt.cjswere processing\{\\{\#if}}markers inside fenced code blocks, corrupting their content when rendering workflow prompts.Why
When a workflow's prompt (via
\{\\{\#runtime-import}}) includes fenced code blocks that contain Handlebars-style strings — for example, JavaScript test harness code with inputs like"\{\\{\#if false}}\nHidden\n\{\{/if}}"— the template renderer would see those markers and process them as real conditionals, stripping the content from the string literals.This corrupted the
daily-rendering-scripts-verifierworkflow's Phase 5 test harness: the test cases arrived at the agent with their\{\\{\#if false}}inputs already processed, leaving wrong inputs that caused the render tests to fail incorrectly.Fix
Before processing
\{\\{\#if}}conditionals, extract all fenced code blocks (triple-backtick fences) and replace them with NUL-byte placeholders. After processing, restore the original code block content.Applied identically to both files since each has its own copy of
renderMarkdownTemplate.Test Results
render_templatetest suite: 17/17 passed (no regressions)interpolate_prompttest suite: 60/60 passed (no regressions)render_template.cjsapplied to the workflow markdown now preserves\{\\{\#if false}}inside fenced code blocksRoot Cause Discovery
Triggered by run §24029642787 (Daily Go Function Namer,
claudeengine). The render test harness generated from the workflow instructions had corrupted test inputs, which led to investigation revealing the code-block escaping bug.References: