Summary
HCL's two template escapes, $${ and %%{, mean a literal ${ and %{.
strip_string_quotes=True asks for a string's value rather than its source,
and resolves \n, \r, \t, \", \\ and the unicode escapes -- but not
these two. The value therefore keeps a doubled sigil that Terraform does not.
Reproduction
import hcl2
from hcl2.utils import SerializationOptions
value = SerializationOptions(preserve_heredocs=False, strip_string_quotes=True)
hcl2.loads('a = <<EOT\n$${esc}\nEOT\n', serialization_options=value)["a"] # '$${esc}'
hcl2.loads('a = <<EOT\n%%{d}\nEOT\n', serialization_options=value)["a"] # '%%{d}'
hcl2.loads('a = "$${esc}"\n', serialization_options=value)["a"] # '$${esc}'
OpenTofu evaluates the same three to "${esc}\n", "%{d}\n" and "${esc}".
Both string forms are affected, so the fix is not heredoc-specific: the quoted
form goes through StringRule.serialize (hcl2/rules/strings.py:115 at
v8.1.3, resolving escapes via _serialize_part_as_value at :133), the
heredoc form through HeredocTemplateRule.serialize (:164). Neither
consults the two template escapes; hcl2/utils.py:50 _SIMPLE_ESCAPES lists
the five backslash forms it does resolve.
Suggested shape of the fix
Resolve the two sequences wherever the value form is produced. $$ not
followed by { is literal in both implementations, and so is %% -- the rule
is exactly $${ and %%{, not the doubled sigil on its own.
This issue, and the investigation behind it, were produced by an AI assistant (Claude) working on behalf of the author. Please review with that provenance in mind.
Summary
HCL's two template escapes,
$${and%%{, mean a literal${and%{.strip_string_quotes=Trueasks for a string's value rather than its source,and resolves
\n,\r,\t,\",\\and the unicode escapes -- but notthese two. The value therefore keeps a doubled sigil that Terraform does not.
Reproduction
OpenTofu evaluates the same three to
"${esc}\n","%{d}\n"and"${esc}".Both string forms are affected, so the fix is not heredoc-specific: the quoted
form goes through
StringRule.serialize(hcl2/rules/strings.py:115atv8.1.3, resolving escapes via_serialize_part_as_valueat:133), theheredoc form through
HeredocTemplateRule.serialize(:164). Neitherconsults the two template escapes;
hcl2/utils.py:50_SIMPLE_ESCAPESliststhe five backslash forms it does resolve.
Suggested shape of the fix
Resolve the two sequences wherever the value form is produced.
$$notfollowed by
{is literal in both implementations, and so is%%-- the ruleis exactly
$${and%%{, not the doubled sigil on its own.This issue, and the investigation behind it, were produced by an AI assistant (Claude) working on behalf of the author. Please review with that provenance in mind.