Skip to content

Add job_runs lifecycle.triggers.on_value_change - #6341

Open
radakam wants to merge 12 commits into
mainfrom
deco-job-runs-lifecycle-on-value-change
Open

radakam wants to merge 12 commits into
mainfrom
deco-job-runs-lifecycle-on-value-change

Conversation

@radakam

@radakam radakam commented Aug 21, 2026 •

Copy link
Copy Markdown
Contributor

Changes

Adds lifecycle.triggers.on_value_change for resources.job_runs (direct engine): re-fire the run when the interpolated value of a watched expression changes, e.g. a variable, ${workspace.root_path}, or another resource's id. ${resources.*} references in the expression become deploy dependencies.

Resolved values are stored in state in trigger order and compared by position: changing or adding a watch re-fires the run, removing the last watches does not, and removing or reordering any other watch does.

Why

on_bundle_deploy and on_file_change already re-fire a run on deploy and on file changes. on_value_change covers re-running when a config value the run depends on changes.

Tests

Acceptance tests in acceptance/bundle/resources/job_runs/on_value_change* (values, references, empty value, errors) and acceptance/bundle/python/job_runs-on_value_change (watches defined in YAML, in Python, and added by a Python mutator).

This pull request and its description were written by Isaac.

@radakam
radakam marked this pull request as ready for review August 21, 2026 15:59
@github-actions

github-actions Bot commented Aug 21, 2026 •

Copy link
Copy Markdown
Contributor

Approval status: pending

/acceptance/bundle/ - needs approval

18 files changed
Suggested: @janniklasrose
Also eligible: @denik, @andrewnester, @pietern, @shreyas-goenka, @lennartkats-db, @anton-107

/bundle/ - needs approval

12 files changed
Suggested: @janniklasrose
Also eligible: @denik, @andrewnester, @pietern, @shreyas-goenka, @lennartkats-db, @anton-107

General files (require maintainer)

Files: .nextchanges/bundles/job-runs-on-value-change.md, python/databricks/bundles/job_runs/_models/job_run_trigger.py
Based on git history:

  • @janniklasrose -- recent work in bundle/config/mutator/, bundle/schema/, .nextchanges/bundles/

Any maintainer (@andrewnester, @anton-107, @denik, @pietern, @shreyas-goenka, @simonfaltum, @renaudhartert-db, @janniklasrose, @lennartkats-db, @rugpanov, @rclarey) can approve all areas.
See OWNERS for ownership rules.

@eng-dev-ecosystem-bot

eng-dev-ecosystem-bot commented Aug 21, 2026 •

Copy link
Copy Markdown
Collaborator

✅ Integration test report

Commit: 5d9dfa5

Run: 42647897331

Env ✅​pass 🙈​skip Time
✅​ aws linux-2core-8gb 271 21 4:54
✅​ aws-windows-latest-4core-16gb 273 19 3:25
✅​ azure linux-2core-8gb 270 21 4:28
✅​ azure-windows-latest-4core-16gb 272 19 4:44
✅​ gcp linux-2core-8gb 271 21 4:31
✅​ gcp-windows-latest-4core-16gb 273 19 4:19
Top 4 slowest tests (at least 2 minutes):
duration env testname
3:11 aws-windows-latest-4core-16gb TestAccept
3:06 azure-windows-latest-4core-16gb TestAccept
3:02 gcp-windows-latest-4core-16gb TestAccept
2:03 aws linux-2core-8gb TestAccept

@radakam
radakam force-pushed the deco-job-runs-lifecycle-on-value-change branch from 0db1f4b to e431088 Compare August 24, 2026 09:36
@radakam
radakam force-pushed the deco-job-runs-lifecycle-on-file-change branch 3 times, most recently from d65e640 to b0437a3 Compare August 25, 2026 10:12
@radakam
radakam marked this pull request as draft August 26, 2026 13:27
@radakam
radakam requested a review from denik August 27, 2026 17:18
@radakam
radakam marked this pull request as ready for review August 27, 2026 17:19
@denik
denik force-pushed the deco-job-runs-lifecycle-on-file-change branch 6 times, most recently from 69a8871 to cfc94b7 Compare September 9, 2026 14:12
@denik
denik force-pushed the deco-job-runs-lifecycle-on-file-change branch from 24f9f1f to 6391342 Compare September 10, 2026 12:52
Base automatically changed from deco-job-runs-lifecycle-on-file-change to main September 11, 2026 08:23
@denik
denik force-pushed the deco-job-runs-lifecycle-on-value-change branch 2 times, most recently from e303d92 to aa5189f Compare September 14, 2026 11:15
@denik
denik force-pushed the deco-job-runs-lifecycle-on-value-change branch from aa5189f to fb7ee59 Compare September 21, 2026 11:55
@denik
denik force-pushed the deco-job-runs-lifecycle-on-value-change branch from fb7ee59 to f529e5b Compare October 5, 2026 09:37
@denik
denik requested review from a team as code owners October 5, 2026 09:37
@github-actions github-actions Bot added the DABs DABs related issues label Oct 5, 2026
@denik
denik force-pushed the deco-job-runs-lifecycle-on-value-change branch from f529e5b to c682c05 Compare October 5, 2026 09:55
radakam and others added 3 commits November 9, 2026 11:23
Adds `lifecycle.triggers.on_value_change` for `resources.job_runs` (direct
engine): watch an interpolated expression (a variable, workspace field, or a
${resources.*} reference) and re-fire the run when its resolved value changes.

- Snapshot the expression before interpolation and use the trimmed form as the
  stable state key (SnapshotJobRunValueTriggers), so a value change is a
  modification of one entry rather than a change to the set of watches.
- Resolution reuses existing mechanisms: normal variable interpolation resolves
  ${var.*}/${workspace.*} into triggers_state, and the planner resolves
  ${resources.*} via the references PrepareInputConfig registers, so the deploy
  graph depends on them. The resolved value is stored in state directly.
- Removing or adding a watch is classified in OverrideChangeDesc alongside
  on_file_change: adding/changing recreates the run, removing keeps it.

Built on the on_file_change work (#6309): no PersistState, path.String()-based
change classification, prevent_destroy left to the generic checkForPreventDestroy.

Co-authored-by: Isaac <no-reply@databricks.com>
…y is allowed

Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
@denik
denik force-pushed the deco-job-runs-lifecycle-on-value-change branch from c682c05 to 94850c1 Compare November 9, 2026 10:29
@github-actions

github-actions Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Commit: 5d9dfa5e4 | Workflow run

Regression test report

Selected 6 changed tests (limit=20, 0 not selected)

14 tests: regression=14 unreleased=0 coverage=0 failing=0
test branch main (df79350) latest (v1.25.0)
TestAccept/bundle/python/job_runs-on_value_change/DMS=/PYDAB_VERSION=current ✅ ❌ ❌
TestAccept/bundle/python/job_runs-on_value_change/DMS=true/PYDAB_VERSION=current ✅ ❌ ❌
TestAccept/bundle/refschema/DMS= ✅ ❌ ❌
TestAccept/bundle/refschema/DMS=true ✅ ❌ ❌
TestAccept/bundle/resources/job_runs/on_value_change/DMS= ✅ ❌ ❌
TestAccept/bundle/resources/job_runs/on_value_change/DMS=true ✅ ❌ ❌
TestAccept/bundle/resources/job_runs/on_value_change_empty/DMS= ✅ ❌ ❌
TestAccept/bundle/resources/job_runs/on_value_change_empty/DMS=true ✅ ❌ ❌
TestAccept/bundle/resources/job_runs/on_value_change_errors/DMS= ✅ ❌ ❌
TestAccept/bundle/resources/job_runs/on_value_change_errors/DMS=true ✅ ❌ ❌
TestAccept/bundle/resources/job_runs/on_value_change_ref/DMS=/READPLAN= ✅ ❌ ❌
TestAccept/bundle/resources/job_runs/on_value_change_ref/DMS=/READPLAN=1 ✅ ❌ ❌
TestAccept/bundle/resources/job_runs/on_value_change_ref/DMS=true/READPLAN= ✅ ❌ ❌
TestAccept/bundle/resources/job_runs/on_value_change_ref/DMS=true/READPLAN=1 ✅ ❌ ❌
TestAccept/bundle/python/job_runs-on_value_change/DMS=/PYDAB_VERSION=current: branch ✅ | main (df79350) ❌ | latest (v1.25.0) ❌

main (df79350):

acceptance_test.go:1345: Diff:
        --- bundle/python/job_runs-on_value_change/output.txt
        +++ /tmp/TestAcceptbundlepythonjob_runs-on_value_changeDMS=PYDAB_VER118612704/002/output.txt
        @@ -1,84 +1,23 @@
         
         === validate resolves watches from YAML, from Python and from a Python mutator
         >>> uv run [UV_ARGS] -q [CLI] bundle validate --output json
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.yaml_run.lifecycle.triggers[0]
        +  in databricks.yml:32:13
        +
        +Error: unknown field: on_value_change
        +  at resources.job_runs.py_run.lifecycle.triggers[0]
        +  in __generated_by_python__.yml:1:509
        +
         {
        -  "py_run": {
        -    "job_id": "${resources.jobs.my_job.id}",
        -    "lifecycle": {
        -      "triggers": [
        -        {
        -          "on_value_change": "x1"
        -        },
        -        {
        -          "on_value_change": "y1"
        -        }
        -      ],
        -      "triggers_state": {
        -        "on_value_change": [
        -          "x1",
        -          "y1"
        -        ]
        -      }
        -    }
        -  },
           "yaml_run": {
             "job_id": "${resources.jobs.my_job.id}",
             "lifecycle": {
               "triggers": [
        -        {
        -          "on_value_change": "x1"
        -        },
        -        {
        -          "on_value_change": "y1"
        -        }
        -      ],
        -      "triggers_state": {
        -        "on_value_change": [
        -          "x1",
        -          "y1"
        -        ]
        -      }
        +        {}
        +      ]
             }
           }
         }
         
        -=== first deploy runs both
        ->>> uv run [UV_ARGS] -q [CLI] bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files...
        -Created job_runs.py_run
        -Created job_runs.yaml_run
        -Created jobs.my_job
        -Files: 0 uploaded, 0 deleted
        -Resources: 3 created, 0 changed, 0 deleted, 0 unchanged
        -
        -=== redeploy with unchanged values plans nothing
        ->>> uv run [UV_ARGS] -q [CLI] bundle plan
        -Plan: 0 to add, 0 to change, 0 to delete, 3 unchanged
        -
        -=== changing the variable watched in YAML and Python re-fires both runs
        ->>> uv run [UV_ARGS] -q [CLI] bundle plan
        -recreate job_runs.py_run
        -recreate job_runs.yaml_run
        -
        -Plan: 2 to add, 0 to change, 2 to delete, 1 unchanged
        -
        ->>> uv run [UV_ARGS] -q [CLI] bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files...
        -Recreated job_runs.py_run
        -Recreated job_runs.yaml_run
        -Files: 0 uploaded, 0 deleted
        -Resources: 2 created, 0 changed, 2 deleted, 1 unchanged
        -
        -=== changing the variable the Python mutator watches re-fires both runs
        ->>> uv run [UV_ARGS] -q [CLI] bundle plan
        -recreate job_runs.py_run
        -recreate job_runs.yaml_run
        -
        -Plan: 2 to add, 0 to change, 2 to delete, 1 unchanged
        -
        ->>> uv run [UV_ARGS] -q [CLI] bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files...
        -Recreated job_runs.py_run
        -Recreated job_runs.yaml_run
        -Files: 0 uploaded, 0 deleted
        -Resources: 2 created, 0 changed, 2 deleted, 1 unchanged
        +Exit code: 1

latest (v1.25.0): same output as main (df79350)

TestAccept/bundle/python/job_runs-on_value_change/DMS=true/PYDAB_VERSION=current: branch ✅ | main (df79350) ❌ | latest (v1.25.0) ❌

main (df79350):

acceptance_test.go:1345: Diff:
        --- bundle/python/job_runs-on_value_change/output.txt
        +++ /tmp/TestAcceptbundlepythonjob_runs-on_value_changeDMS=truePYDAB2701649203/002/output.txt
        @@ -1,84 +1,23 @@
         
         === validate resolves watches from YAML, from Python and from a Python mutator
         >>> uv run [UV_ARGS] -q [CLI] bundle validate --output json
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.yaml_run.lifecycle.triggers[0]
        +  in databricks.yml:32:13
        +
        +Error: unknown field: on_value_change
        +  at resources.job_runs.py_run.lifecycle.triggers[0]
        +  in __generated_by_python__.yml:1:509
        +
         {
        -  "py_run": {
        -    "job_id": "${resources.jobs.my_job.id}",
        -    "lifecycle": {
        -      "triggers": [
        -        {
        -          "on_value_change": "x1"
        -        },
        -        {
        -          "on_value_change": "y1"
        -        }
        -      ],
        -      "triggers_state": {
        -        "on_value_change": [
        -          "x1",
        -          "y1"
        -        ]
        -      }
        -    }
        -  },
           "yaml_run": {
             "job_id": "${resources.jobs.my_job.id}",
             "lifecycle": {
               "triggers": [
        -        {
        -          "on_value_change": "x1"
        -        },
        -        {
        -          "on_value_change": "y1"
        -        }
        -      ],
        -      "triggers_state": {
        -        "on_value_change": [
        -          "x1",
        -          "y1"
        -        ]
        -      }
        +        {}
        +      ]
             }
           }
         }
         
        -=== first deploy runs both
        ->>> uv run [UV_ARGS] -q [CLI] bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files...
        -Created job_runs.py_run
        -Created job_runs.yaml_run
        -Created jobs.my_job
        -Files: 0 uploaded, 0 deleted
        -Resources: 3 created, 0 changed, 0 deleted, 0 unchanged
        -
        -=== redeploy with unchanged values plans nothing
        ->>> uv run [UV_ARGS] -q [CLI] bundle plan
        -Plan: 0 to add, 0 to change, 0 to delete, 3 unchanged
        -
        -=== changing the variable watched in YAML and Python re-fires both runs
        ->>> uv run [UV_ARGS] -q [CLI] bundle plan
        -recreate job_runs.py_run
        -recreate job_runs.yaml_run
        -
        -Plan: 2 to add, 0 to change, 2 to delete, 1 unchanged
        -
        ->>> uv run [UV_ARGS] -q [CLI] bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files...
        -Recreated job_runs.py_run
        -Recreated job_runs.yaml_run
        -Files: 0 uploaded, 0 deleted
        -Resources: 2 created, 0 changed, 2 deleted, 1 unchanged
        -
        -=== changing the variable the Python mutator watches re-fires both runs
        ->>> uv run [UV_ARGS] -q [CLI] bundle plan
        -recreate job_runs.py_run
        -recreate job_runs.yaml_run
        -
        -Plan: 2 to add, 0 to change, 2 to delete, 1 unchanged
        -
        ->>> uv run [UV_ARGS] -q [CLI] bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files...
        -Recreated job_runs.py_run
        -Recreated job_runs.yaml_run
        -Files: 0 uploaded, 0 deleted
        -Resources: 2 created, 0 changed, 2 deleted, 1 unchanged
        +Exit code: 1

latest (v1.25.0): same output as main (df79350)

TestAccept/bundle/refschema/DMS=: branch ✅ | main (df79350) ❌ | latest (v1.25.0) ❌

main (df79350):

acceptance_test.go:1345: Diff:
        --- bundle/refschema/output/job_runs.txt
        +++ /tmp/TestAcceptbundlerefschemaDMS=1878220307/001/output/job_runs.txt
        @@ -19,13 +19,10 @@
         resources.job_runs.*.lifecycle.triggers[*]	resources.JobRunTrigger	INPUT
         resources.job_runs.*.lifecycle.triggers[*].on_bundle_deploy	*bool	INPUT
         resources.job_runs.*.lifecycle.triggers[*].on_file_change	*string	INPUT
        -resources.job_runs.*.lifecycle.triggers[*].on_value_change	*string	INPUT
         resources.job_runs.*.lifecycle.triggers_state	*resources.JobRunTriggersState	INPUT	STATE
         resources.job_runs.*.lifecycle.triggers_state.on_bundle_deploy	string	INPUT	STATE
         resources.job_runs.*.lifecycle.triggers_state.on_file_change	map[string]string	INPUT	STATE
         resources.job_runs.*.lifecycle.triggers_state.on_file_change.*	string	INPUT	STATE
        -resources.job_runs.*.lifecycle.triggers_state.on_value_change	[]string	INPUT	STATE
        -resources.job_runs.*.lifecycle.triggers_state.on_value_change[*]	string	INPUT	STATE
         resources.job_runs.*.modified_status	string	INPUT
         resources.job_runs.*.notebook_params	map[string]string	ALL
         resources.job_runs.*.notebook_params.*	string	ALL

latest (v1.25.0): same output as main (df79350)

TestAccept/bundle/refschema/DMS=true: branch ✅ | main (df79350) ❌ | latest (v1.25.0) ❌

main (df79350):

acceptance_test.go:1345: Diff:
        --- bundle/refschema/output/job_runs.txt
        +++ /tmp/TestAcceptbundlerefschemaDMS=true4083321581/001/output/job_runs.txt
        @@ -19,13 +19,10 @@
         resources.job_runs.*.lifecycle.triggers[*]	resources.JobRunTrigger	INPUT
         resources.job_runs.*.lifecycle.triggers[*].on_bundle_deploy	*bool	INPUT
         resources.job_runs.*.lifecycle.triggers[*].on_file_change	*string	INPUT
        -resources.job_runs.*.lifecycle.triggers[*].on_value_change	*string	INPUT
         resources.job_runs.*.lifecycle.triggers_state	*resources.JobRunTriggersState	INPUT	STATE
         resources.job_runs.*.lifecycle.triggers_state.on_bundle_deploy	string	INPUT	STATE
         resources.job_runs.*.lifecycle.triggers_state.on_file_change	map[string]string	INPUT	STATE
         resources.job_runs.*.lifecycle.triggers_state.on_file_change.*	string	INPUT	STATE
        -resources.job_runs.*.lifecycle.triggers_state.on_value_change	[]string	INPUT	STATE
        -resources.job_runs.*.lifecycle.triggers_state.on_value_change[*]	string	INPUT	STATE
         resources.job_runs.*.modified_status	string	INPUT
         resources.job_runs.*.notebook_params	map[string]string	ALL
         resources.job_runs.*.notebook_params.*	string	ALL

latest (v1.25.0): same output as main (df79350)

TestAccept/bundle/resources/job_runs/on_value_change/DMS=: branch ✅ | main (df79350) ❌ | latest (v1.25.0) ❌

main (df79350):

acceptance_test.go:1345: Diff:
        --- bundle/resources/job_runs/on_value_change/output.txt
        +++ /tmp/TestAcceptbundleresourcesjob_runson_value_changeDMS=514751401/002/output.txt
        @@ -1,205 +1,63 @@
         
         === first deploy triggers a run
         >>> [CLI] bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change/default/files...
        -Created jobs.my_job
        -Output from job_runs.my_run: id=[MY_RUN_ID]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID]?o=[WORKSPACE_ID]
        -Output from job_runs.my_run: id=[MY_RUN_ID]: SUCCESS
        -Created job_runs.my_run
        -Files: 2 uploaded, 0 deleted
        -Resources: 2 created, 0 changed, 0 deleted, 0 unchanged
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.my_run.lifecycle.triggers[1]
        +  in databricks.yml:25:52
         
        ->>> read_id.py my_job
        -[MY_JOB_ID]
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.my_run.lifecycle.triggers[2]
        +  in databricks.yml:25:87
         
        ->>> print_requests.py //jobs/run-now
        -{
        -  "method": "POST",
        -  "path": "/api/2.2/jobs/run-now",
        -  "body": {
        -    "idempotency_token": "[UUID]",
        -    "job_id": [MY_JOB_ID]
        -  }
        -}
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.my_run.lifecycle.triggers[3]
        +  in databricks.yml:25:122
         
        -=== resolved values are stored in state in trigger order
        ->>> print_state.py
        -{
        -  "on_file_change": {
        -    "watched.txt": "[FILE_HASH][0]"
        -  },
        -  "on_value_change": [
        -    "v1",
        -    "a1",
        -    "42",
        -    "/Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change/default"
        -  ]
        -}
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.my_run.lifecycle.triggers[4]
        +  in databricks.yml:25:158
         
        -=== redeploy with unchanged values plans nothing
        ->>> [CLI] bundle plan
        -Plan: 0 to add, 0 to change, 0 to delete, 2 unchanged
        +Error: lifecycle.triggers entry must set on_bundle_deploy or on_file_change
        +  in databricks.yml:25:51
         
        ->>> [CLI] bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change/default/files...
        -Files: 0 uploaded, 0 deleted
        -Resources: 0 created, 0 changed, 0 deleted, 2 unchanged
        +Error: lifecycle.triggers entry must set on_bundle_deploy or on_file_change
        +  in databricks.yml:25:86
         
        ->>> print_requests.py //jobs/run-now
        +Error: lifecycle.triggers entry must set on_bundle_deploy or on_file_change
        +  in databricks.yml:25:121
         
        -=== changing one value re-fires
        ->>> changes
        -{
        -  "lifecycle.triggers_state.on_value_change[0]": {
        -    "action": "recreate",
        -    "reason": "immutable",
        -    "old": "v1",
        -    "new": "v2"
        -  },
        -  "result_state": {
        -    "action": "skip",
        -    "reason": "remote_already_set",
        -    "new": "SUCCESS",
        -    "remote": "SUCCESS"
        -  }
        -}
        +Error: lifecycle.triggers entry must set on_bundle_deploy or on_file_change
        +  in databricks.yml:25:157
         
        ->>> [CLI] bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change/default/files...
        -Output from job_runs.my_run: id=[MY_RUN_ID_2]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID_2]?o=[WORKSPACE_ID]
        -Output from job_runs.my_run: id=[MY_RUN_ID_2]: SUCCESS
        -Recreated job_runs.my_run
        -Files: 1 uploaded, 0 deleted
        -Resources: 1 created, 0 changed, 1 deleted, 1 unchanged
         
        ->>> print_requests.py --keep //jobs/runs/delete
        +>>> [CLI] bundle destroy --auto-approve
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.my_run.lifecycle.triggers[1]
        +  in databricks.yml:25:52
         
        ->>> print_requests.py //jobs/run-now
        -{
        -  "method": "POST",
        -  "path": "/api/2.2/jobs/run-now",
        -  "body": {
        -    "idempotency_token": "[UUID]",
        -    "job_id": [MY_JOB_ID]
        -  }
        -}
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.my_run.lifecycle.triggers[2]
        +  in databricks.yml:25:87
         
        -=== removing the last on_value_change through a saved plan does not run
        ->>> [CLI] bundle plan
        -Plan: 0 to add, 0 to change, 0 to delete, 2 unchanged
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.my_run.lifecycle.triggers[3]
        +  in databricks.yml:25:122
         
        ->>> [CLI] bundle deploy --plan tmp.plan.json
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change/default/files...
        -Files: 2 uploaded, 0 deleted
        -Resources: 0 created, 0 changed, 0 deleted, 2 unchanged
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.my_run.lifecycle.triggers[4]
        +  in databricks.yml:25:158
         
        ->>> print_requests.py //jobs/run-now
        +Error: lifecycle.triggers entry must set on_bundle_deploy or on_file_change
        +  in databricks.yml:25:51
         
        -=== re-adding it with an unchanged value does not re-fire
        ->>> [CLI] bundle plan
        -Plan: 0 to add, 0 to change, 0 to delete, 2 unchanged
        +Error: lifecycle.triggers entry must set on_bundle_deploy or on_file_change
        +  in databricks.yml:25:86
         
        ->>> [CLI] bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change/default/files...
        -Files: 1 uploaded, 0 deleted
        -Resources: 0 created, 0 changed, 0 deleted, 2 unchanged
        +Error: lifecycle.triggers entry must set on_bundle_deploy or on_file_change
        +  in databricks.yml:25:121
         
        ->>> print_requests.py //jobs/run-now
        +Error: lifecycle.triggers entry must set on_bundle_deploy or on_file_change
        +  in databricks.yml:25:157
         
        -=== removing an on_value_change that is not the last shifts the rest and re-fires
        ->>> changes
        -{
        -  "lifecycle.triggers_state.on_value_change": {
        -    "action": "recreate",
        -    "reason": "immutable",
        -    "old": [
        -      "v2",
        -      "a1",
        -      "42",
        -      "/Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change/default"
        -    ],
        -    "new": [
        -      "v2",
        -      "42",
        -      "/Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change/default"
        -    ]
        -  },
        -  "result_state": {
        -    "action": "skip",
        -    "reason": "remote_already_set",
        -    "new": "SUCCESS",
        -    "remote": "SUCCESS"
        -  }
        -}
         
        ->>> [CLI] bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change/default/files...
        -Output from job_runs.my_run: id=[MY_RUN_ID_3]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID_3]?o=[WORKSPACE_ID]
        -Output from job_runs.my_run: id=[MY_RUN_ID_3]: SUCCESS
        -Recreated job_runs.my_run
        -Files: 1 uploaded, 0 deleted
        -Resources: 1 created, 0 changed, 1 deleted, 1 unchanged
        -
        ->>> print_requests.py //jobs/run-now
        -{
        -  "method": "POST",
        -  "path": "/api/2.2/jobs/run-now",
        -  "body": {
        -    "idempotency_token": "[UUID]",
        -    "job_id": [MY_JOB_ID]
        -  }
        -}
        -
        -=== removing the remaining on_value_change entries does not run
        ->>> [CLI] bundle plan
        -Plan: 0 to add, 0 to change, 0 to delete, 2 unchanged
... (51 more lines truncated, raise DATABRICKS_CLI_TEST_MAX_LOG to see more)

latest (v1.25.0): same output as main (df79350)

TestAccept/bundle/resources/job_runs/on_value_change/DMS=true: branch ✅ | main (df79350) ❌ | latest (v1.25.0) ❌

main (df79350):

acceptance_test.go:1345: Diff:
        --- bundle/resources/job_runs/on_value_change/output.txt
        +++ /tmp/TestAcceptbundleresourcesjob_runson_value_changeDMS=true3357115142/002/output.txt
        @@ -1,205 +1,63 @@
         
         === first deploy triggers a run
         >>> [CLI] bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change/default/files...
        -Created jobs.my_job
        -Output from job_runs.my_run: id=[MY_RUN_ID]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID]?o=[WORKSPACE_ID]
        -Output from job_runs.my_run: id=[MY_RUN_ID]: SUCCESS
        -Created job_runs.my_run
        -Files: 2 uploaded, 0 deleted
        -Resources: 2 created, 0 changed, 0 deleted, 0 unchanged
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.my_run.lifecycle.triggers[1]
        +  in databricks.yml:25:52
         
        ->>> read_id.py my_job
        -[MY_JOB_ID]
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.my_run.lifecycle.triggers[2]
        +  in databricks.yml:25:87
         
        ->>> print_requests.py //jobs/run-now
        -{
        -  "method": "POST",
        -  "path": "/api/2.2/jobs/run-now",
        -  "body": {
        -    "idempotency_token": "[UUID]",
        -    "job_id": [MY_JOB_ID]
        -  }
        -}
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.my_run.lifecycle.triggers[3]
        +  in databricks.yml:25:122
         
        -=== resolved values are stored in state in trigger order
        ->>> print_state.py
        -{
        -  "on_file_change": {
        -    "watched.txt": "[FILE_HASH][0]"
        -  },
        -  "on_value_change": [
        -    "v1",
        -    "a1",
        -    "42",
        -    "/Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change/default"
        -  ]
        -}
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.my_run.lifecycle.triggers[4]
        +  in databricks.yml:25:158
         
        -=== redeploy with unchanged values plans nothing
        ->>> [CLI] bundle plan
        -Plan: 0 to add, 0 to change, 0 to delete, 2 unchanged
        +Error: lifecycle.triggers entry must set on_bundle_deploy or on_file_change
        +  in databricks.yml:25:51
         
        ->>> [CLI] bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change/default/files...
        -Files: 0 uploaded, 0 deleted
        -Resources: 0 created, 0 changed, 0 deleted, 2 unchanged
        +Error: lifecycle.triggers entry must set on_bundle_deploy or on_file_change
        +  in databricks.yml:25:86
         
        ->>> print_requests.py //jobs/run-now
        +Error: lifecycle.triggers entry must set on_bundle_deploy or on_file_change
        +  in databricks.yml:25:121
         
        -=== changing one value re-fires
        ->>> changes
        -{
        -  "lifecycle.triggers_state.on_value_change[0]": {
        -    "action": "recreate",
        -    "reason": "immutable",
        -    "old": "v1",
        -    "new": "v2"
        -  },
        -  "result_state": {
        -    "action": "skip",
        -    "reason": "remote_already_set",
        -    "new": "SUCCESS",
        -    "remote": "SUCCESS"
        -  }
        -}
        +Error: lifecycle.triggers entry must set on_bundle_deploy or on_file_change
        +  in databricks.yml:25:157
         
        ->>> [CLI] bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change/default/files...
        -Output from job_runs.my_run: id=[MY_RUN_ID_2]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID_2]?o=[WORKSPACE_ID]
        -Output from job_runs.my_run: id=[MY_RUN_ID_2]: SUCCESS
        -Recreated job_runs.my_run
        -Files: 1 uploaded, 0 deleted
        -Resources: 1 created, 0 changed, 1 deleted, 1 unchanged
         
        ->>> print_requests.py --keep //jobs/runs/delete
        +>>> [CLI] bundle destroy --auto-approve
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.my_run.lifecycle.triggers[1]
        +  in databricks.yml:25:52
         
        ->>> print_requests.py //jobs/run-now
        -{
        -  "method": "POST",
        -  "path": "/api/2.2/jobs/run-now",
        -  "body": {
        -    "idempotency_token": "[UUID]",
        -    "job_id": [MY_JOB_ID]
        -  }
        -}
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.my_run.lifecycle.triggers[2]
        +  in databricks.yml:25:87
         
        -=== removing the last on_value_change through a saved plan does not run
        ->>> [CLI] bundle plan
        -Plan: 0 to add, 0 to change, 0 to delete, 2 unchanged
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.my_run.lifecycle.triggers[3]
        +  in databricks.yml:25:122
         
        ->>> [CLI] bundle deploy --plan tmp.plan.json
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change/default/files...
        -Files: 2 uploaded, 0 deleted
        -Resources: 0 created, 0 changed, 0 deleted, 2 unchanged
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.my_run.lifecycle.triggers[4]
        +  in databricks.yml:25:158
         
        ->>> print_requests.py //jobs/run-now
        +Error: lifecycle.triggers entry must set on_bundle_deploy or on_file_change
        +  in databricks.yml:25:51
         
        -=== re-adding it with an unchanged value does not re-fire
        ->>> [CLI] bundle plan
        -Plan: 0 to add, 0 to change, 0 to delete, 2 unchanged
        +Error: lifecycle.triggers entry must set on_bundle_deploy or on_file_change
        +  in databricks.yml:25:86
         
        ->>> [CLI] bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change/default/files...
        -Files: 1 uploaded, 0 deleted
        -Resources: 0 created, 0 changed, 0 deleted, 2 unchanged
        +Error: lifecycle.triggers entry must set on_bundle_deploy or on_file_change
        +  in databricks.yml:25:121
         
        ->>> print_requests.py //jobs/run-now
        +Error: lifecycle.triggers entry must set on_bundle_deploy or on_file_change
        +  in databricks.yml:25:157
         
        -=== removing an on_value_change that is not the last shifts the rest and re-fires
        ->>> changes
        -{
        -  "lifecycle.triggers_state.on_value_change": {
        -    "action": "recreate",
        -    "reason": "immutable",
        -    "old": [
        -      "v2",
        -      "a1",
        -      "42",
        -      "/Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change/default"
        -    ],
        -    "new": [
        -      "v2",
        -      "42",
        -      "/Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change/default"
        -    ]
        -  },
        -  "result_state": {
        -    "action": "skip",
        -    "reason": "remote_already_set",
        -    "new": "SUCCESS",
        -    "remote": "SUCCESS"
        -  }
        -}
         
        ->>> [CLI] bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change/default/files...
        -Output from job_runs.my_run: id=[MY_RUN_ID_3]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID_3]?o=[WORKSPACE_ID]
        -Output from job_runs.my_run: id=[MY_RUN_ID_3]: SUCCESS
        -Recreated job_runs.my_run
        -Files: 1 uploaded, 0 deleted
        -Resources: 1 created, 0 changed, 1 deleted, 1 unchanged
        -
        ->>> print_requests.py //jobs/run-now
        -{
        -  "method": "POST",
        -  "path": "/api/2.2/jobs/run-now",
        -  "body": {
        -    "idempotency_token": "[UUID]",
        -    "job_id": [MY_JOB_ID]
        -  }
        -}
        -
        -=== removing the remaining on_value_change entries does not run
        ->>> [CLI] bundle plan
        -Plan: 0 to add, 0 to change, 0 to delete, 2 unchanged
... (51 more lines truncated, raise DATABRICKS_CLI_TEST_MAX_LOG to see more)

latest (v1.25.0):

acceptance_test.go:1345: Diff:
        --- bundle/resources/job_runs/on_value_change/output.txt
        +++ /tmp/TestAcceptbundleresourcesjob_runson_value_changeDMS=true436187475/002/output.txt
        @@ -1,205 +1,63 @@
         
         === first deploy triggers a run
         >>> [CLI] bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change/default/files...
        -Created jobs.my_job
        -Output from job_runs.my_run: id=[MY_RUN_ID]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID]?o=[WORKSPACE_ID]
        -Output from job_runs.my_run: id=[MY_RUN_ID]: SUCCESS
        -Created job_runs.my_run
        -Files: 2 uploaded, 0 deleted
        -Resources: 2 created, 0 changed, 0 deleted, 0 unchanged
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.my_run.lifecycle.triggers[1]
        +  in databricks.yml:25:52
         
        ->>> read_id.py my_job
        -[MY_JOB_ID]
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.my_run.lifecycle.triggers[2]
        +  in databricks.yml:25:87
         
        ->>> print_requests.py //jobs/run-now
        -{
        -  "method": "POST",
        -  "path": "/api/2.2/jobs/run-now",
        -  "body": {
        -    "idempotency_token": "[UUID]",
        -    "job_id": [MY_JOB_ID]
        -  }
        -}
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.my_run.lifecycle.triggers[3]
        +  in databricks.yml:25:122
         
        -=== resolved values are stored in state in trigger order
        ->>> print_state.py
        -{
        -  "on_file_change": {
        -    "watched.txt": "[FILE_HASH][0]"
        -  },
        -  "on_value_change": [
        -    "v1",
        -    "a1",
        -    "42",
        -    "/Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change/default"
        -  ]
        -}
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.my_run.lifecycle.triggers[4]
        +  in databricks.yml:25:158
         
        -=== redeploy with unchanged values plans nothing
        ->>> [CLI] bundle plan
        -Plan: 0 to add, 0 to change, 0 to delete, 2 unchanged
        +Error: lifecycle.triggers entry must set on_bundle_deploy or on_file_change
        +  in databricks.yml:25:51
         
        ->>> [CLI] bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change/default/files...
        -Files: 0 uploaded, 0 deleted
        -Resources: 0 created, 0 changed, 0 deleted, 2 unchanged
        +Error: lifecycle.triggers entry must set on_bundle_deploy or on_file_change
        +  in databricks.yml:25:86
         
        ->>> print_requests.py //jobs/run-now
        +Error: lifecycle.triggers entry must set on_bundle_deploy or on_file_change
        +  in databricks.yml:25:121
         
        -=== changing one value re-fires
        ->>> changes
        -{
        -  "lifecycle.triggers_state.on_value_change[0]": {
        -    "action": "recreate",
        -    "reason": "immutable",
        -    "old": "v1",
        -    "new": "v2"
        -  },
        -  "result_state": {
        -    "action": "skip",
        -    "reason": "remote_already_set",
        -    "new": "SUCCESS",
        -    "remote": "SUCCESS"
        -  }
        -}
        +Error: lifecycle.triggers entry must set on_bundle_deploy or on_file_change
        +  in databricks.yml:25:157
         
        ->>> [CLI] bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change/default/files...
        -Output from job_runs.my_run: id=[MY_RUN_ID_2]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID_2]?o=[WORKSPACE_ID]
        -Output from job_runs.my_run: id=[MY_RUN_ID_2]: SUCCESS
        -Recreated job_runs.my_run
        -Files: 1 uploaded, 0 deleted
        -Resources: 1 created, 0 changed, 1 deleted, 1 unchanged
         
        ->>> print_requests.py --keep //jobs/runs/delete
        +>>> [CLI] bundle destroy --auto-approve
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.my_run.lifecycle.triggers[1]
        +  in databricks.yml:25:52
         
        ->>> print_requests.py //jobs/run-now
        -{
        -  "method": "POST",
        -  "path": "/api/2.2/jobs/run-now",
        -  "body": {
        -    "idempotency_token": "[UUID]",
        -    "job_id": [MY_JOB_ID]
        -  }
        -}
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.my_run.lifecycle.triggers[2]
        +  in databricks.yml:25:87
         
        -=== removing the last on_value_change through a saved plan does not run
        ->>> [CLI] bundle plan
        -Plan: 0 to add, 0 to change, 0 to delete, 2 unchanged
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.my_run.lifecycle.triggers[3]
        +  in databricks.yml:25:122
         
        ->>> [CLI] bundle deploy --plan tmp.plan.json
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change/default/files...
        -Files: 2 uploaded, 0 deleted
        -Resources: 0 created, 0 changed, 0 deleted, 2 unchanged
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.my_run.lifecycle.triggers[4]
        +  in databricks.yml:25:158
         
        ->>> print_requests.py //jobs/run-now
        +Error: lifecycle.triggers entry must set on_bundle_deploy or on_file_change
        +  in databricks.yml:25:51
         
        -=== re-adding it with an unchanged value does not re-fire
        ->>> [CLI] bundle plan
        -Plan: 0 to add, 0 to change, 0 to delete, 2 unchanged
        +Error: lifecycle.triggers entry must set on_bundle_deploy or on_file_change
        +  in databricks.yml:25:86
         
        ->>> [CLI] bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change/default/files...
        -Files: 1 uploaded, 0 deleted
        -Resources: 0 created, 0 changed, 0 deleted, 2 unchanged
        +Error: lifecycle.triggers entry must set on_bundle_deploy or on_file_change
        +  in databricks.yml:25:121
         
        ->>> print_requests.py //jobs/run-now
        +Error: lifecycle.triggers entry must set on_bundle_deploy or on_file_change
        +  in databricks.yml:25:157
         
        -=== removing an on_value_change that is not the last shifts the rest and re-fires
        ->>> changes
        -{
        -  "lifecycle.triggers_state.on_value_change": {
        -    "action": "recreate",
        -    "reason": "immutable",
        -    "old": [
        -      "v2",
        -      "a1",
        -      "42",
        -      "/Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change/default"
        -    ],
        -    "new": [
        -      "v2",
        -      "42",
        -      "/Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change/default"
        -    ]
        -  },
        -  "result_state": {
        -    "action": "skip",
        -    "reason": "remote_already_set",
        -    "new": "SUCCESS",
        -    "remote": "SUCCESS"
        -  }
        -}
         
        ->>> [CLI] bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change/default/files...
        -Output from job_runs.my_run: id=[MY_RUN_ID_3]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID_3]?o=[WORKSPACE_ID]
        -Output from job_runs.my_run: id=[MY_RUN_ID_3]: SUCCESS
        -Recreated job_runs.my_run
        -Files: 1 uploaded, 0 deleted
        -Resources: 1 created, 0 changed, 1 deleted, 1 unchanged
        -
        ->>> print_requests.py //jobs/run-now
        -{
        -  "method": "POST",
        -  "path": "/api/2.2/jobs/run-now",
        -  "body": {
        -    "idempotency_token": "[UUID]",
        -    "job_id": [MY_JOB_ID]
        -  }
        -}
        -
        -=== removing the remaining on_value_change entries does not run
        ->>> [CLI] bundle plan
        -Plan: 0 to add, 0 to change, 0 to delete, 2 unchanged
... (53 more lines truncated, raise DATABRICKS_CLI_TEST_MAX_LOG to see more)
TestAccept/bundle/resources/job_runs/on_value_change_empty/DMS=: branch ✅ | main (df79350) ❌ | latest (v1.25.0) ❌

main (df79350):

acceptance_test.go:1345: Diff:
        --- bundle/resources/job_runs/on_value_change_empty/output.txt
        +++ /tmp/TestAcceptbundleresourcesjob_runson_value_change_emptyDMS=3093969756/002/output.txt
        @@ -1,104 +1,26 @@
         
         === validate keeps the empty value
         >>> [CLI] bundle validate -o json
        -{
        -  "triggers": [
        -    {
        -      "on_value_change": ""
        -    }
        -  ],
        -  "triggers_state": {
        -    "on_value_change": [
        -      ""
        -    ]
        -  }
        -}
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.my_run.lifecycle.triggers[0]
        +  in databricks.yml:21:13
         
        -=== a watch on an empty value deploys and records it
        ->>> [CLI] bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files...
        -Created jobs.my_job
        -Output from job_runs.my_run: id=[MY_RUN_ID]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID]?o=[WORKSPACE_ID]
        -Output from job_runs.my_run: id=[MY_RUN_ID]: SUCCESS
        -Created job_runs.my_run
        -Files: 1 uploaded, 0 deleted
        -Resources: 2 created, 0 changed, 0 deleted, 0 unchanged
        +Error: lifecycle.triggers entry must set on_bundle_deploy or on_file_change
        +  in databricks.yml:21:13
         
        ->>> print_requests.py //jobs/run-now
         {
        -  "method": "POST",
        -  "path": "/api/2.2/jobs/run-now",
        -  "body": {
        -    "idempotency_token": "[UUID]",
        -    "job_id": [MY_JOB_ID]
        -  }
        +  "triggers": [
        +    {}
        +  ]
         }
         
        ->>> print_state.py
        -{
        -  "triggers_state": {
        -    "on_value_change": [
        -      ""
        -    ]
        -  }
        -}
        -
        -=== redeploy with the value still empty plans nothing
        ->>> [CLI] bundle plan
        -Plan: 0 to add, 0 to change, 0 to delete, 2 unchanged
        -
        -=== setting the value re-fires
        ->>> [CLI] bundle plan
        -recreate job_runs.my_run
        -
        -Plan: 1 to add, 0 to change, 1 to delete, 1 unchanged
        -
        ->>> [CLI] bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files...
        -Output from job_runs.my_run: id=[MY_RUN_ID_2]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID_2]?o=[WORKSPACE_ID]
        -Output from job_runs.my_run: id=[MY_RUN_ID_2]: SUCCESS
        -Recreated job_runs.my_run
        -Files: 1 uploaded, 0 deleted
        -Resources: 1 created, 0 changed, 1 deleted, 1 unchanged
        -
        ->>> print_requests.py //jobs/run-now
        -{
        -  "method": "POST",
        -  "path": "/api/2.2/jobs/run-now",
        -  "body": {
        -    "idempotency_token": "[UUID]",
        -    "job_id": [MY_JOB_ID]
        -  }
        -}
        -
        -=== clearing the value again re-fires
        ->>> [CLI] bundle plan
        -recreate job_runs.my_run
        -
        -Plan: 1 to add, 0 to change, 1 to delete, 1 unchanged
        -
        ->>> [CLI] bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files...
        -Output from job_runs.my_run: id=[MY_RUN_ID_3]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID_3]?o=[WORKSPACE_ID]
        -Output from job_runs.my_run: id=[MY_RUN_ID_3]: SUCCESS
        -Recreated job_runs.my_run
        -Files: 1 uploaded, 0 deleted
        -Resources: 1 created, 0 changed, 1 deleted, 1 unchanged
        -
        ->>> print_requests.py //jobs/run-now
        -{
        -  "method": "POST",
        -  "path": "/api/2.2/jobs/run-now",
        -  "body": {
        -    "idempotency_token": "[UUID]",
        -    "job_id": [MY_JOB_ID]
        -  }
        -}
        -
         >>> [CLI] bundle destroy --auto-approve
        -The following resources will be deleted:
        -  delete resources.jobs.my_job
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.my_run.lifecycle.triggers[0]
        +  in databricks.yml:21:13
         
        -All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-bundle/default
        +Error: lifecycle.triggers entry must set on_bundle_deploy or on_file_change
        +  in databricks.yml:21:13
         
        -Destroy: 1 deleted
        +
        +Exit code: 1

latest (v1.25.0): same output as main (df79350)

TestAccept/bundle/resources/job_runs/on_value_change_empty/DMS=true: branch ✅ | main (df79350) ❌ | latest (v1.25.0) ❌

main (df79350):

acceptance_test.go:1345: Diff:
        --- bundle/resources/job_runs/on_value_change_empty/output.txt
        +++ /tmp/TestAcceptbundleresourcesjob_runson_value_change_emptyDMS=t1866256758/002/output.txt
        @@ -1,104 +1,26 @@
         
         === validate keeps the empty value
         >>> [CLI] bundle validate -o json
        -{
        -  "triggers": [
        -    {
        -      "on_value_change": ""
        -    }
        -  ],
        -  "triggers_state": {
        -    "on_value_change": [
        -      ""
        -    ]
        -  }
        -}
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.my_run.lifecycle.triggers[0]
        +  in databricks.yml:21:13
         
        -=== a watch on an empty value deploys and records it
        ->>> [CLI] bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files...
        -Created jobs.my_job
        -Output from job_runs.my_run: id=[MY_RUN_ID]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID]?o=[WORKSPACE_ID]
        -Output from job_runs.my_run: id=[MY_RUN_ID]: SUCCESS
        -Created job_runs.my_run
        -Files: 1 uploaded, 0 deleted
        -Resources: 2 created, 0 changed, 0 deleted, 0 unchanged
        +Error: lifecycle.triggers entry must set on_bundle_deploy or on_file_change
        +  in databricks.yml:21:13
         
        ->>> print_requests.py //jobs/run-now
         {
        -  "method": "POST",
        -  "path": "/api/2.2/jobs/run-now",
        -  "body": {
        -    "idempotency_token": "[UUID]",
        -    "job_id": [MY_JOB_ID]
        -  }
        +  "triggers": [
        +    {}
        +  ]
         }
         
        ->>> print_state.py
        -{
        -  "triggers_state": {
        -    "on_value_change": [
        -      ""
        -    ]
        -  }
        -}
        -
        -=== redeploy with the value still empty plans nothing
        ->>> [CLI] bundle plan
        -Plan: 0 to add, 0 to change, 0 to delete, 2 unchanged
        -
        -=== setting the value re-fires
        ->>> [CLI] bundle plan
        -recreate job_runs.my_run
        -
        -Plan: 1 to add, 0 to change, 1 to delete, 1 unchanged
        -
        ->>> [CLI] bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files...
        -Output from job_runs.my_run: id=[MY_RUN_ID_2]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID_2]?o=[WORKSPACE_ID]
        -Output from job_runs.my_run: id=[MY_RUN_ID_2]: SUCCESS
        -Recreated job_runs.my_run
        -Files: 1 uploaded, 0 deleted
        -Resources: 1 created, 0 changed, 1 deleted, 1 unchanged
        -
        ->>> print_requests.py //jobs/run-now
        -{
        -  "method": "POST",
        -  "path": "/api/2.2/jobs/run-now",
        -  "body": {
        -    "idempotency_token": "[UUID]",
        -    "job_id": [MY_JOB_ID]
        -  }
        -}
        -
        -=== clearing the value again re-fires
        ->>> [CLI] bundle plan
        -recreate job_runs.my_run
        -
        -Plan: 1 to add, 0 to change, 1 to delete, 1 unchanged
        -
        ->>> [CLI] bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files...
        -Output from job_runs.my_run: id=[MY_RUN_ID_3]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID_3]?o=[WORKSPACE_ID]
        -Output from job_runs.my_run: id=[MY_RUN_ID_3]: SUCCESS
        -Recreated job_runs.my_run
        -Files: 1 uploaded, 0 deleted
        -Resources: 1 created, 0 changed, 1 deleted, 1 unchanged
        -
        ->>> print_requests.py //jobs/run-now
        -{
        -  "method": "POST",
        -  "path": "/api/2.2/jobs/run-now",
        -  "body": {
        -    "idempotency_token": "[UUID]",
        -    "job_id": [MY_JOB_ID]
        -  }
        -}
        -
         >>> [CLI] bundle destroy --auto-approve
        -The following resources will be deleted:
        -  delete resources.jobs.my_job
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.my_run.lifecycle.triggers[0]
        +  in databricks.yml:21:13
         
        -All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-bundle/default
        +Error: lifecycle.triggers entry must set on_bundle_deploy or on_file_change
        +  in databricks.yml:21:13
         
        -Destroy: 1 deleted
        +
        +Exit code: 1

latest (v1.25.0): same output as main (df79350)

TestAccept/bundle/resources/job_runs/on_value_change_errors/DMS=: branch ✅ | main (df79350) ❌ | latest (v1.25.0) ❌

main (df79350):

acceptance_test.go:1345: Diff:
        --- bundle/resources/job_runs/on_value_change_errors/output.txt
        +++ /tmp/TestAcceptbundleresourcesjob_runson_value_change_errorsDMS=1480518489/002/output.txt
        @@ -1,8 +1,9 @@
         
         === multiple trigger fields in one entry are rejected
         >>> musterr [CLI] bundle validate
        -Error: lifecycle.triggers entry must set only one of on_bundle_deploy, on_file_change, or on_value_change
        -  in databricks.yml:24:20
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.my_run.lifecycle.triggers[0]
        +  in databricks.yml:24:50
         
         Name: job-runs-on-value-change-errors
         Target: default
        @@ -10,33 +11,8 @@
           User: [USERNAME]
           Path: /Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change-errors/default
         
        -Found 1 error
        +Found 1 warning
         
        -=== an undefined variable is rejected
        ->>> musterr [CLI] bundle validate
        -Error: reference does not exist: ${var.missing}
        +Unexpected success
         
        -Name: job-runs-on-value-change-errors
        -Target: default
        -Workspace:
        -  User: [USERNAME]
        -  Path: /Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change-errors/default
        -
        -Found 1 error
        -
        -=== a complex value is rejected
        ->>> musterr [CLI] bundle validate
        -Warning: expected string, found map
        -  at resources.job_runs.my_run.lifecycle.triggers[0].on_value_change
        -  in databricks.yml:24:38
        -
        -Error: lifecycle.triggers entry must set on_bundle_deploy, on_file_change, or on_value_change
        -  in databricks.yml:24:20
        -
        -Name: job-runs-on-value-change-errors
        -Target: default
        -Workspace:
        -  User: [USERNAME]
        -  Path: /Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change-errors/default
        -
        -Found 1 error and 1 warning
        +Exit code: 1
        
    acceptance_test.go:1135: Test produced unexpected files:
        .databricks/.gitignore

latest (v1.25.0): same output as main (df79350)

TestAccept/bundle/resources/job_runs/on_value_change_errors/DMS=true: branch ✅ | main (df79350) ❌ | latest (v1.25.0) ❌

main (df79350):

acceptance_test.go:1345: Diff:
        --- bundle/resources/job_runs/on_value_change_errors/output.txt
        +++ /tmp/TestAcceptbundleresourcesjob_runson_value_change_errorsDMS=1159117130/002/output.txt
        @@ -1,8 +1,9 @@
         
         === multiple trigger fields in one entry are rejected
         >>> musterr [CLI] bundle validate
        -Error: lifecycle.triggers entry must set only one of on_bundle_deploy, on_file_change, or on_value_change
        -  in databricks.yml:24:20
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.my_run.lifecycle.triggers[0]
        +  in databricks.yml:24:50
         
         Name: job-runs-on-value-change-errors
         Target: default
        @@ -10,33 +11,8 @@
           User: [USERNAME]
           Path: /Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change-errors/default
         
        -Found 1 error
        +Found 1 warning
         
        -=== an undefined variable is rejected
        ->>> musterr [CLI] bundle validate
        -Error: reference does not exist: ${var.missing}
        +Unexpected success
         
        -Name: job-runs-on-value-change-errors
        -Target: default
        -Workspace:
        -  User: [USERNAME]
        -  Path: /Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change-errors/default
        -
        -Found 1 error
        -
        -=== a complex value is rejected
        ->>> musterr [CLI] bundle validate
        -Warning: expected string, found map
        -  at resources.job_runs.my_run.lifecycle.triggers[0].on_value_change
        -  in databricks.yml:24:38
        -
        -Error: lifecycle.triggers entry must set on_bundle_deploy, on_file_change, or on_value_change
        -  in databricks.yml:24:20
        -
        -Name: job-runs-on-value-change-errors
        -Target: default
        -Workspace:
        -  User: [USERNAME]
        -  Path: /Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change-errors/default
        -
        -Found 1 error and 1 warning
        +Exit code: 1
        
    acceptance_test.go:1135: Test produced unexpected files:
        .databricks/.gitignore

latest (v1.25.0): same output as main (df79350)

TestAccept/bundle/resources/job_runs/on_value_change_ref/DMS=/READPLAN=: branch ✅ | main (df79350) ❌ | latest (v1.25.0) ❌

main (df79350):

acceptance_test.go:1345: Diff:
        --- bundle/resources/job_runs/on_value_change_ref/output.txt
        +++ /tmp/TestAcceptbundleresourcesjob_runson_value_change_refDMS=RE4272198115/002/output.txt
        @@ -1,121 +1,33 @@
         
        -=== plan depends on both jobs in the expressions
        ->>> jq .plan["resources.job_runs.my_run"].depends_on tmp.plan.json
        -[
        -  {
        -    "node": "resources.jobs.extra",
        -    "label": "${resources.jobs.extra.id}"
        -  },
        -  {
        -    "node": "resources.jobs.my_job",
        -    "label": "${resources.jobs.my_job.id}"
        -  },
        -  {
        -    "node": "resources.jobs.other",
        -    "label": "${resources.jobs.other.id}"
        -  }
        -]
        +=== plan depends on both jobs in the expressionsWarning: unknown field: on_value_change
        +  at resources.job_runs.my_run.lifecycle.triggers[0]
        +  in databricks.yml:36:13
         
        -=== first deploy triggers a run
        -=== bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change-ref/default/files...
        -Created jobs.extra
        -Created jobs.my_job
        -Created jobs.other
        -Output from job_runs.my_run: id=[MY_RUN_ID]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID]?o=[WORKSPACE_ID]
        -Output from job_runs.my_run: id=[MY_RUN_ID]: SUCCESS
        -Created job_runs.my_run
        -Files: 2 uploaded, 0 deleted
        -Resources: 4 created, 0 changed, 0 deleted, 0 unchanged
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.my_run.lifecycle.triggers[1]
        +  in databricks.yml:37:13
         
        ->>> read_id.py my_job
        -[MY_JOB_ID]
        +Error: lifecycle.triggers entry must set on_bundle_deploy or on_file_change
        +  in databricks.yml:36:13
         
        ->>> read_id.py other
        -[OTHER_ID]
        +Error: lifecycle.triggers entry must set on_bundle_deploy or on_file_change
        +  in databricks.yml:37:13
         
        ->>> read_id.py extra
        -[EXTRA_ID]
         
        ->>> print_requests.py //jobs/run-now
        -{
        -  "method": "POST",
        -  "path": "/api/2.2/jobs/run-now",
        -  "body": {
        -    "idempotency_token": "[UUID]",
        -    "job_id": [MY_JOB_ID]
        -  }
        -}
        +>>> [CLI] bundle destroy --auto-approve
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.my_run.lifecycle.triggers[0]
        +  in databricks.yml:36:13
         
        -=== state records resolved values for pure and mixed references
        ->>> print_state.py
        -[
        -  "[OTHER_ID]",
        -  "[OTHER_ID]-suffix-[EXTRA_ID]"
        -]
        +Warning: unknown field: on_value_change
        +  at resources.job_runs.my_run.lifecycle.triggers[1]
        +  in databricks.yml:37:13
         
        -=== redeploy with unchanged ids plans nothing
        ->>> [CLI] bundle plan
        -Plan: 0 to add, 0 to change, 0 to delete, 4 unchanged
        +Error: lifecycle.triggers entry must set on_bundle_deploy or on_file_change
        +  in databricks.yml:36:13
         
        ->>> [CLI] bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change-ref/default/files...
        -Files: 0 uploaded, 0 deleted
        -Resources: 0 created, 0 changed, 0 deleted, 4 unchanged
        +Error: lifecycle.triggers entry must set on_bundle_deploy or on_file_change
        +  in databricks.yml:37:13
         
        ->>> print_requests.py //jobs/run-now
         
        -=== updating a referenced job in place does not re-fire the run
        ->>> [CLI] bundle plan
        -update jobs.other
        -
        -Plan: 0 to add, 1 to change, 0 to delete, 3 unchanged
        -
        ->>> [CLI] bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change-ref/default/files...
        -Updated jobs.other
        -Files: 1 uploaded, 0 deleted
        -Resources: 0 created, 1 changed, 0 deleted, 3 unchanged
        -
        ->>> print_requests.py //jobs/run-now
        -
        -=== replacing a referenced job re-fires the run
        ->>> [CLI] jobs delete [OTHER_ID]
        -
        ->>> [CLI] bundle plan
        -recreate job_runs.my_run
        -create jobs.other
        -
        -Plan: 2 to add, 0 to change, 1 to delete, 2 unchanged
        -
        ->>> [CLI] bundle deploy
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change-ref/default/files...
        -Created jobs.other
        -Output from job_runs.my_run: id=[MY_RUN_ID_2]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID_2]?o=[WORKSPACE_ID]
        -Output from job_runs.my_run: id=[MY_RUN_ID_2]: SUCCESS
        -Recreated job_runs.my_run
        -Files: 0 uploaded, 0 deleted
        -Resources: 2 created, 0 changed, 1 deleted, 2 unchanged
        -
        ->>> read_id.py other
        -[OTHER_ID_3]
        -
        ->>> print_requests.py //jobs/run-now
        -{
        -  "method": "POST",
        -  "path": "/api/2.2/jobs/run-now",
        -  "body": {
        -    "idempotency_token": "[UUID]",
        -    "job_id": [MY_JOB_ID]
        -  }
        -}
        -
        ->>> [CLI] bundle destroy --auto-approve
        -The following resources will be deleted:
        -  delete resources.jobs.extra
        -  delete resources.jobs.my_job
        -  delete resources.jobs.other
        -
        -All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/job-runs-on-value-change-ref/default
        -
        -Destroy: 3 deleted
        +Exit code: 1

latest (v1.25.0): same output as main (df79350)

3 more failure details omitted, see the full report in the run.

denik and others added 2 commits November 9, 2026 13:05
Record today's behavior, including the bugs:

- python/job_runs-on_value_change: any bundle with a python: section and an
  on_value_change watch fails validate with "Unexpected field
  'triggers_state' for class JobRunLifecycle", for YAML- and Python-defined
  job_runs alike.
- on_value_change_empty: a watched variable whose value is "" fails with
  "lifecycle.triggers entry must set ..." on an entry that does set it.
- on_value_change_errors: a complex variable gives two "expected string,
  found map" warnings (one at the internal triggers_state path, at :0:0)
  and the same misleading error.
- on_value_change_ref: updating a referenced job in place does not re-fire
  the run (correct; locks it in).

Co-authored-by: Isaac <no-reply@databricks.com>
SnapshotJobRunValueTriggers ran in ProcessStaticResources, before
PythonMutator, so Python saw the internal lifecycle.triggers_state, which
the PyDABs JobRunLifecycle model does not declare: any bundle with a
python: section and an on_value_change watch failed with "Unexpected field
'triggers_state' for class JobRunLifecycle".

on_value_change expressions are now never resolved in place
(dynvar.ResolveExcept, keepUnresolved), so the expression survives until
after PythonMutator, where it is copied into triggers_state and resolved
there (ResolveJobRunValueTriggerReferences). This also lets a Python
mutator add a watch, drops the "already snapshotted" guard, and fixes a
watched value of "": the trigger entry keeps its expression, so
DropEmptyStrings no longer empties it.

A complex value now only warns; the next commit rejects it.

Co-authored-by: Isaac <no-reply@databricks.com>
@github-actions github-actions Bot added the PyDABs label Nov 9, 2026
denik and others added 7 commits November 9, 2026 13:21
The value is stored as a string, so normalization dropped a complex value
with a warning and the watch silently never fired. ValidateJobRunTriggers
now reports it. The snapshot is written to the dynamic config with the
trigger's location, so the normalization warning points at the trigger
instead of ":0:0".

The old snapshot unit test edited typed config between two applies, which
the next apply overwrote from the dynamic config, so it never reached the
guard it meant to test; replace it with one for the current behavior.

Co-authored-by: Isaac <no-reply@databricks.com>
Removing a watch keeps its last value in state, the same as on_file_change,
so re-adding it with an unchanged value does not re-fire the run. The field
description said any added watch re-fires, and the test step that shows
the opposite was titled "re-fires". Describe the rule as a comparison with
the value recorded when the run last fired, and fix the title.

Co-authored-by: Isaac <no-reply@databricks.com>
The planner's extractReferences already registers the ${resources.*}
references in lifecycle.triggers_state.on_value_change from the dynamic
config, so PrepareInputConfig added the same refs a second time; the README
reserves the hook for sub-resources. The maps.Clone in PrepareState
protected entries from fingerprinting that no longer exists.

Co-authored-by: Isaac <no-reply@databricks.com>
Iterating the job_runs map made the order of diagnostics from several
job_runs random; ResolveJobRunFileTriggers sorts for the same reason. The
[VALUE_HASH] replacement in on_value_change_ref was left over from when
values were stored as hashes and matches nothing.

Co-authored-by: Isaac <no-reply@databricks.com>
Replace the resolver change from the previous commits: on_value_change no
longer stays unresolved (dynvar.ResolveExcept and keepUnresolved are
gone), so the trigger shows its resolved value like any other field.

SnapshotJobRunValueTriggers records the expressions on the Bundle before
variable resolution (as RootPathIsNameTargetScoped does), and after
PythonMutator ResolveJobRunValueTriggers stores the resolved trigger
values in triggers_state under them. Python never sees triggers_state.
Values are paired with expressions by trigger position, so a Python
mutator that adds or removes triggers of a job_run with on_value_change
gets an error.

Co-authored-by: Isaac <no-reply@databricks.com>
Replace the expression-keyed state with the interpolated values in trigger
order. ResolveJobRunValueTriggers copies them into triggers_state after
PythonMutator; nothing is recorded before interpolation, so the snapshot
mutator, the Bundle field and the Python mutator limitation are gone, and
duplicate or blank expressions are no longer errors.

The planner compares the values per position: a changed or added value
re-fires the run, and a list that keeps the leading values of the old one
(watches removed from the end) does not. Removing or reordering any other
watch shifts the values after it, which re-fires.

ValidateJobRunTriggers moves before DropEmptyStrings, which would drop a
watched "" value and leave an entry that sets nothing.

Co-authored-by: Isaac <no-reply@databricks.com>
DropEmptyStrings exists so "" on an omitempty field is not force-sent to the
backend. lifecycle is never sent, and there an empty string is a value:
job_runs lifecycle.triggers.on_value_change can watch a variable that is "".
Dropping it left a trigger entry that sets nothing.

With lifecycle left alone, ValidateJobRunTriggers goes back to its place
after DropEmptyStrings.

Co-authored-by: Isaac <no-reply@databricks.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

DABs DABs related issues PyDABs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants