Describe the issue
A string field (e.g. a job's base_parameters) sometimes needs to contain a literal ${name} for its own downstream purposes — unrelated to any bundle variable, resource substitution, or Terraform interpolation. Under the Terraform engine, this was correctly escaped using Terraform's documented $${...} convention (double dollar sign).
After our bundle was migrated to the direct engine, $${...} is no longer recognized as escaped. Neither is the alternative \${...} (backslash) convention. Both are treated identically to a fully unescaped ${...} and fail with "invalid dependency" errors.
Why we need this
Job parameters are often the input to a generic, metadata-driven ingestion notebook: a single notebook implementation is shared across many similar jobs, and each job supplies its own JSON "config template" plus a list of concrete parameter sets for each source it ingests. At runtime, our own notebook code resolves placeholders like ${source_file}, ${business_date[0..9]} (a custom slicing syntax we implement ourselves, not standard Databricks/Python syntax), ${batch_id}, etc. against values from the job's own declared parameters ({{job.id}}, {{job.start_time.iso_date}}, ...) and from the matching entry in a parallel parameters_list array. None of this resolution happens via, or is related to, Databricks bundle variables or Terraform interpolation -- the bundle's only job is to deploy these string values as written; substitution happens entirely inside our own notebook code, after the job has already started running.
A simplified, representative excerpt (structure preserved, business-specific names genericized):
resources:
jobs:
example_ingestion_job:
tasks:
- task_key: run_generic_ingestion
notebook_task:
notebook_path: src/Notebooks/common/elt/Generic_Runs
base_parameters:
generic_config: '{"source": {"path": "landing/$${p_folder}/$${source_file}_*.csv"}, "schema": {"columns": [{"name": "w_batch_id", "expression": "$${batch_id}"}, {"name": "w_business_dt", "expression": "cast(''$${business_date[0..9]}'' as date)"}]}, "target": {"table": "${var.catalog_name}.$${p_env_stg}.$${target_table}"}}'
parameters_list: '[{"source_file": "TableA", "target_table": "table_a", "p_env_stg": "stg_example", "p_folder": "foldera"}, {"source_file": "TableB", "target_table": "table_b", "p_env_stg": "stg_example", "p_folder": "folderb"}]'
parameters:
- name: batch_id
default: '{{job.id}}'
- name: business_date
default: '{{job.start_time.iso_date}}'
Note ${var.catalog_name} sitting in the same string as several $${...}-escaped literals -- a genuine bundle variable that does need to resolve, right alongside content that must not be. Any fix needs to let both coexist correctly in the same field, the same way they did under the Terraform engine.
This pattern appears in dozens of our job definitions -- metadata-driven ingestion is our standard approach for any source with a repeatable ingest-and-load shape -- so this isn't an edge case for us. It currently blocks every deploy, and every "invalid dependency" error we've hit in production (${source_file}, ${business_date}, ${batch_id}, ${folder}, each on a different attempt) is the same underlying problem surfacing on whichever escaped placeholder the dependency-graph pass happens to reach first -- not several unrelated bugs.
Direct question for maintainers: is there a currently-supported way to escape a literal ${...} in a string field under the direct engine? We couldn't find one documented, and our minimal repro below shows neither of the two conventions we tried works.
Steps to reproduce
Minimal databricks.yml:
bundle:
name: escape_test
workspace:
host: https://<workspace-host>
root_path: /Workspace/Users/<username>/escape_test
resources:
jobs:
escape_test_job:
name: escape_test_job
tasks:
- task_key: test
notebook_task:
notebook_path: /Workspace/Users/<username>/dummy
base_parameters:
option_a: '$${test_var}'
Run:
Result:
Error: invalid dependency "${test_var}", no such node ""
Now replace the parameter with the backslash form instead:
Same result, same error shape:
Error: invalid dependency "${test_var}", no such node ""
A fully unescaped control case ('${test_var}', no leading character at all) fails identically. In every case, the error message shows exactly one $ before {test_var} — regardless of whether the source had $$, \$, or a single unescaped $. This suggests the dependency-graph pass is scanning for the literal substring ${ anywhere in the string, independent of whatever character precedes it, rather than checking for either escape convention.
Expected behavior
Some mechanism — ideally $${...}, matching Terraform's own documented escaping and our prior working behavior — should let a literal ${...} appear in a string field without being treated as an interpolation reference, the same way it worked under the Terraform engine.
Actual behavior
$${...}, \${...}, and a fully unescaped ${...} all fail identically with invalid dependency "${name}", no such node "".
Is this a regression?
Yes. This exact $${...} pattern worked correctly under the Terraform engine (last confirmed working on CLI v1.13.0). It broke after our bundle was automatically migrated to the direct engine — triggered by upgrading to CLI v1.14.0, per that release's note: "Bundles still on Terraform state are now migrated to the direct engine automatically, after a deploy whose dry-run conversion comes back clean."
We also confirmed this can't be reverted by setting bundle.engine: terraform locally — the CLI reports:
Warning: Deployment engine "terraform" configured in bundle.engine setting ... does not match the existing state (engine "direct"). Using "direct" engine from the existing state.
The remote state itself now forces the direct engine regardless of local configuration, so there's no way back to the previously-working behavior for an already-migrated bundle.
OS and CLI version
- OS: Windows
- CLI version tested: Databricks CLI v1.14.1
- Also reproduced in our CI pipeline (Azure DevOps, Ubuntu hosted agent) on CLI v1.14.1
Additional context
This pattern is common for us across dozens of job definitions — job parameters frequently hold JSON-encoded configuration blobs where application-level placeholders (e.g. ${source_file}, ${business_date}) are resolved by downstream notebook code at runtime, not by the bundle itself. All of these were previously escaped correctly via Terraform's $$ convention. We couldn't find documentation describing the direct engine's equivalent for this specific case — the docs we found (interpolation parser rewrite, "Bash convention" \$ escaping) describe escape handling for malformed/typo'd variable references (e.g. catching ${var.my_clster_id} as a likely typo), which appears to be a different code path from escaping a deliberately literal, non-bundle ${...} in an arbitrary string field.
Describe the issue
A string field (e.g. a job's
base_parameters) sometimes needs to contain a literal${name}for its own downstream purposes — unrelated to any bundle variable, resource substitution, or Terraform interpolation. Under the Terraform engine, this was correctly escaped using Terraform's documented$${...}convention (double dollar sign).After our bundle was migrated to the direct engine,
$${...}is no longer recognized as escaped. Neither is the alternative\${...}(backslash) convention. Both are treated identically to a fully unescaped${...}and fail with "invalid dependency" errors.Why we need this
Job parameters are often the input to a generic, metadata-driven ingestion notebook: a single notebook implementation is shared across many similar jobs, and each job supplies its own JSON "config template" plus a list of concrete parameter sets for each source it ingests. At runtime, our own notebook code resolves placeholders like
${source_file},${business_date[0..9]}(a custom slicing syntax we implement ourselves, not standard Databricks/Python syntax),${batch_id}, etc. against values from the job's own declared parameters ({{job.id}},{{job.start_time.iso_date}}, ...) and from the matching entry in a parallelparameters_listarray. None of this resolution happens via, or is related to, Databricks bundle variables or Terraform interpolation -- the bundle's only job is to deploy these string values as written; substitution happens entirely inside our own notebook code, after the job has already started running.A simplified, representative excerpt (structure preserved, business-specific names genericized):
Note
${var.catalog_name}sitting in the same string as several$${...}-escaped literals -- a genuine bundle variable that does need to resolve, right alongside content that must not be. Any fix needs to let both coexist correctly in the same field, the same way they did under the Terraform engine.This pattern appears in dozens of our job definitions -- metadata-driven ingestion is our standard approach for any source with a repeatable ingest-and-load shape -- so this isn't an edge case for us. It currently blocks every deploy, and every "invalid dependency" error we've hit in production (
${source_file},${business_date},${batch_id},${folder}, each on a different attempt) is the same underlying problem surfacing on whichever escaped placeholder the dependency-graph pass happens to reach first -- not several unrelated bugs.Direct question for maintainers: is there a currently-supported way to escape a literal
${...}in a string field under the direct engine? We couldn't find one documented, and our minimal repro below shows neither of the two conventions we tried works.Steps to reproduce
Minimal
databricks.yml:Run:
Result:
Now replace the parameter with the backslash form instead:
Same result, same error shape:
A fully unescaped control case (
'${test_var}', no leading character at all) fails identically. In every case, the error message shows exactly one$before{test_var}— regardless of whether the source had$$,\$, or a single unescaped$. This suggests the dependency-graph pass is scanning for the literal substring${anywhere in the string, independent of whatever character precedes it, rather than checking for either escape convention.Expected behavior
Some mechanism — ideally
$${...}, matching Terraform's own documented escaping and our prior working behavior — should let a literal${...}appear in a string field without being treated as an interpolation reference, the same way it worked under the Terraform engine.Actual behavior
$${...},\${...}, and a fully unescaped${...}all fail identically withinvalid dependency "${name}", no such node "".Is this a regression?
Yes. This exact
$${...}pattern worked correctly under the Terraform engine (last confirmed working on CLI v1.13.0). It broke after our bundle was automatically migrated to the direct engine — triggered by upgrading to CLI v1.14.0, per that release's note: "Bundles still on Terraform state are now migrated to the direct engine automatically, after a deploy whose dry-run conversion comes back clean."We also confirmed this can't be reverted by setting
bundle.engine: terraformlocally — the CLI reports:The remote state itself now forces the direct engine regardless of local configuration, so there's no way back to the previously-working behavior for an already-migrated bundle.
OS and CLI version
Additional context
This pattern is common for us across dozens of job definitions — job parameters frequently hold JSON-encoded configuration blobs where application-level placeholders (e.g.
${source_file},${business_date}) are resolved by downstream notebook code at runtime, not by the bundle itself. All of these were previously escaped correctly via Terraform's$$convention. We couldn't find documentation describing the direct engine's equivalent for this specific case — the docs we found (interpolation parser rewrite, "Bash convention"\$escaping) describe escape handling for malformed/typo'd variable references (e.g. catching${var.my_clster_id}as a likely typo), which appears to be a different code path from escaping a deliberately literal, non-bundle${...}in an arbitrary string field.