Skip to content

Fix: backslash before {{ template marker treated as literal character#205

Merged
blindzero merged 7 commits intomainfrom
copilot/fix-template-substitution-issue
Feb 22, 2026
Merged

Fix: backslash before {{ template marker treated as literal character#205
blindzero merged 7 commits intomainfrom
copilot/fix-template-substitution-issue

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 21, 2026

Template strings with a backslash immediately before {{ (e.g. Windows domain paths like DOMAIN\{{Request.IdentityKeys.sAMAccountName}}) threw a false "Unbalanced braces" error because the regex used (?<!\\)\{\{ — treating \ as an escape prefix that suppressed the opening brace count while }} was still counted.

Changes

  • Escape mechanism: \{{ remains the escape syntax for a literal {{ in output. A negative lookahead regex restricted to the exact set of allowed roots distinguishes between an escape and a literal backslash before a template: \{{ not followed by a valid allowed-root path+}} produces {{ in output; \{{Request.<allowedRoot>...}} treats \ as a literal character and resolves the template normally. Sequences like \{{Request.InvalidRoot}} or \{{Request..Bad}} are correctly escaped (same "unbalanced braces" error as original code — not sent to template parsing).
  • Regex: Removed (?<!\\) negative lookbehind from all regex patterns in Resolve-IdleTemplateString.ps1. Template matching no longer treats \ as special.
  • Normalization: \{{ (escape form only) is replaced with a Unicode Private Use Area placeholder (U+E001) before matching, then restored to {{ after resolution. Brace counts and error messages both operate on the normalized form; error messages display the original user-supplied value.
  • Fixtures: template-escaped.psd1 and template-escaped-mixed.psd1 use the \{{ escape syntax (unchanged from original). New template-backslash.psd1 and template-escaped-invalid-root.psd1 fixtures added.
  • Test coverage: Added test asserting 'DOMAIN\{{Request.IdentityKeys.sAMAccountName}}' resolves to 'DOMAIN\jdoe'. Added regression test asserting \{{Request.InvalidRoot}} throws "Unbalanced braces" (not a path/root error), locking in backward-compatible behavior.
  • Documentation: Added a "Template substitution" section to docs/use/workflows.md covering syntax, allowed roots, pure vs. mixed placeholders, backslash behaviour, escaping rules, and validation errors.
# Before (threw "Unbalanced braces" error):
IdentityKey = 'DOMAIN\{{Request.IdentityKeys.sAMAccountName}}'

# After (resolves correctly to e.g. 'DOMAIN\jdoe'):
IdentityKey = 'DOMAIN\{{Request.IdentityKeys.sAMAccountName}}'

# Literal {{ in output — \{{ escape still works when not before a valid allowed-root template:
Value = 'Literal \{{ braces here'  # → 'Literal {{ braces here'

# \{{ before an invalid/disallowed root is also escaped (same unbalanced-braces error as original):
Value = '\{{Request.InvalidRoot}}'  # → error: Unbalanced braces
Original prompt

This section details on the original issue you should resolve

<issue_title>Partial template substitution with \ before {{ dont work</issue_title>
<issue_description>## Description

Substitution of template strings {{...}} dont work if "" is part of the whole string directly in front of the {{

Steps to Reproduce

  1. Have a step with Identitykey = 'DOMAIN\{{Request.IdentityKeys.sAMAccountName}}'
  2. Execute New-IdlePlan
  3. Get Error

Also tried with escaping Identitykey = 'DOMAIN\\{{Request.IdentityKeys.sAMAccountName}}' or Identitykey = 'DOMAIN{{Request.IdentityKeys.sAMAccountName}}'` with same error.

Expected Behavior

\ should remain as part of the literal, but {{...}} is substituted.

Actual Behavior

Error

OperationStopped: src\IdLE.Core\Private\Resolve-IdleTemplateString.ps1:224:13 
Line | 
 224 |              throw [System.ArgumentException]::new(
     |              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | Template syntax error in step 'AD - Disable account': Unbalanced braces in value 'NANOTEMPER\{{Request.IdentityKeys.sAMAccountName}}'. Found 0 opening '{{' and 1 closing '}}'. Check for typos or missing braces. (Parameter 'Workflow')

Environment

  • PowerShell version: 7.5.4
  • OS: Windows Server 2019
  • IdLE version / commit: main branch</issue_description>

Comments on the Issue (you are @copilot in this section)


🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix partial template substitution with \ in front of {{ Fix: backslash before {{ template marker treated as literal character Feb 21, 2026
Copilot AI requested a review from blindzero February 21, 2026 22:40
Copy link
Copy Markdown
Owner

@blindzero blindzero left a comment

Choose a reason for hiding this comment

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

The way with double-double (four) braces {{{{ is very counter-intuitive. Can we use any other way of proper escaping here and make sure that \ directly in front of {{ braces it just identified properly and a single literal within '...' string?

…ormally

Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Feb 21, 2026

The way with double-double (four) braces {{{{ is very counter-intuitive. Can we use any other way of proper escaping here and make sure that \ directly in front of {{ braces it just identified properl...

Reverted back to \{{ as the escape syntax (commit 2f76834). The key fix is a negative lookahead in the pre-processing regex: \\{{(?![A-Za-z][A-Za-z0-9_.]*}}).

How it works:

  • \{{ not followed by a valid path+}} → produces literal {{ in output (the escape)
  • \{{path}} where path looks like a template → \ is literal, template resolves normally

So these all work as expected:

# Bug fix: backslash before a template is just a literal backslash
'DOMAIN\{{Request.IdentityKeys.sAMAccountName}}''DOMAIN\jdoe'

# Escape still works for literal {{ (no valid path+}} follows)
'Literal \{{ braces here''Literal {{ braces here'

# Mixed: escape + template in same string
'Literal \{{ and template {{Request.Input.Name}}''Literal {{ and template TestName'

Copy link
Copy Markdown
Owner

@blindzero blindzero left a comment

Choose a reason for hiding this comment

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

@copilot I am missing some documentation to describe this behavior properly. check existing docs for substitutions part

…s.md

Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Feb 21, 2026

@copilot I am missing some documentation to describe this behavior properly. check existing docs for substitutions part

Added in commit aa719af:

  • New: docs/reference/specs/template-substitution.md — full reference covering syntax, allowed roots, pure vs. mixed placeholders, backslash behaviour, escaping rules (\{{), and all validation errors.
  • Updated: docs/reference/intro-reference.md — listed the new spec under Specifications.
  • Updated: docs/use/workflows.md — added a "Template substitution" section with examples (including the DOMAIN\{{...}} case) and a link to the reference page.

Copy link
Copy Markdown
Owner

@blindzero blindzero left a comment

Choose a reason for hiding this comment

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

@copilot I am missing some documentation to describe this behavior properly. check existing docs for substitutions part

Comment thread docs/reference/specs/template-substitution.md Outdated
…e standalone spec file

Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Feb 21, 2026

@copilot I am missing some documentation to describe this behavior properly. check existing docs for substitutions part

Documentation is now in docs/use/workflows.md as a self-contained "Template substitution" section (commit e5e28fc). It covers syntax, allowed roots, pure vs. mixed placeholders, backslash/domain-path behaviour, the \{{ escape, and all validation errors — no separate file needed.

Comment thread docs/use/workflows.md
@blindzero blindzero marked this pull request as ready for review February 21, 2026 23:24
Copilot AI review requested due to automatic review settings February 21, 2026 23:24
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e5e28fc06c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

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

This pull request fixes a bug where template substitution failed when a backslash character appeared immediately before the {{ template marker, even when the backslash was intended as a literal character (e.g., in Windows domain paths like DOMAIN\{{Request.IdentityKeys.sAMAccountName}}).

Changes:

  • Fixed the escape sequence detection logic to use a negative lookahead pattern that distinguishes between escape sequences (\{{ not followed by a valid path) and literal backslashes before templates (\{{path}})
  • Added comprehensive test coverage for the backslash-before-template case
  • Enhanced documentation in docs/use/workflows.md with a complete "Template substitution" section explaining syntax, escaping, and backslash behavior

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
src/IdLE.Core/Private/Resolve-IdleTemplateString.ps1 Core fix: replaced lookbehind-based escape detection with normalization using negative lookahead; Unicode placeholder prevents escaped {{ from being matched as templates
tests/fixtures/workflows/template-tests/template-backslash.psd1 New test fixture for backslash-before-template case (e.g., DOMAIN\{{...}})
tests/Core/Resolve-IdleWorkflowTemplates.Tests.ps1 Added test case validating that backslash before {{ is treated as literal when followed by valid template path
docs/use/workflows.md Added comprehensive template substitution documentation with examples of backslash behavior and escaping rules

Repository owner deleted a comment from chatgpt-codex-connector Bot Feb 21, 2026
Comment thread src/IdLE.Core/Private/Resolve-IdleTemplateString.ps1 Outdated
…test

Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
@blindzero blindzero merged commit cc3c5e3 into main Feb 22, 2026
8 checks passed
@blindzero blindzero deleted the copilot/fix-template-substitution-issue branch February 22, 2026 13:21
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.

Partial template substitution with \ before {{ dont work

3 participants