Skip to content

[rendering-scripts] fix: preserve fenced code blocks in template rendering#24862

Merged
pelikhan merged 2 commits intomainfrom
fix/render-template-skip-code-blocks-9d6c8133d2a27f2f
Apr 6, 2026
Merged

[rendering-scripts] fix: preserve fenced code blocks in template rendering#24862
pelikhan merged 2 commits intomainfrom
fix/render-template-skip-code-blocks-9d6c8133d2a27f2f

Conversation

@github-actions
Copy link
Copy Markdown
Contributor

@github-actions github-actions bot commented Apr 6, 2026

What

render_template.cjs and interpolate_prompt.cjs were 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-verifier workflow'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_template test suite: 17/17 passed (no regressions)
  • interpolate_prompt test suite: 60/60 passed (no regressions)
  • All three suites together: 111/111 passed
  • Manual verification: render_template.cjs applied to the workflow markdown now preserves \{\\{\#if false}} inside fenced code blocks
  • Claude parser on real run §24029642787: exit 0, 3147 bytes summary output

Root Cause Discovery

Triggered by run §24029642787 (Daily Go Function Namer, claude engine). The render test harness generated from the workflow instructions had corrupted test inputs, which led to investigation revealing the code-block escaping bug.

References:

Generated by Daily Rendering Scripts Verifier · ● 1M ·

  • expires on Apr 9, 2026, 11:32 AM UTC

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>
@github-actions github-actions bot added automated-fix javascript Pull requests that update javascript code rendering labels Apr 6, 2026
@pelikhan
Copy link
Copy Markdown
Collaborator

pelikhan commented Apr 6, 2026

@copilot

  • add regression tests
  • add runtime assertions (number of fences before and after are the same)

…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>
Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 6, 2026

@copilot

  • add regression tests
  • add runtime assertions (number of fences before and after are the same)

Done in commit 0110a37:

  • Regression tests: 6 new tests in each of render_template.test.cjs and interpolate_prompt_additional.test.cjs, covering: {{#if false}} inside a fence is preserved, {{#if true}} inside a fence is preserved, conditionals outside fences still process normally, fence count invariant, multiple fenced blocks, fenced block adjacent to an outer conditional.

  • Runtime assertion: After restoring extracted fenced blocks, both render_template.cjs and interpolate_prompt.cjs now count triple-backtick sequences in the input and output and call core.warning(...) if they differ.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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.cjs and interpolate_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.warning for 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

Comment on lines +27 to +32
// 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}`;
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
// 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}`;

Copilot uses AI. Check for mistakes.
Comment on lines +59 to +62
// 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 => {
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
// 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 => {

Copilot uses AI. Check for mistakes.
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```");
});
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
});
});
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);
});

Copilot uses AI. Check for mistakes.
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```");
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
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);

Copilot uses AI. Check for mistakes.
@pelikhan pelikhan merged commit 560634c into main Apr 6, 2026
122 checks passed
@pelikhan pelikhan deleted the fix/render-template-skip-code-blocks-9d6c8133d2a27f2f branch April 6, 2026 14:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automated-fix javascript Pull requests that update javascript code rendering

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants