Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
459d58e
job_runs: add lifecycle.triggers.on_file_change
radakam Aug 18, 2026
9306589
job_runs: fix exhaustruct and regenerate refschema for on_file_change
radakam Aug 18, 2026
c7cf616
job_runs: tighten on_file_change path and directory checks
radakam Aug 18, 2026
6e63c23
job_runs: store on_file_change as path-to-hash only
radakam Aug 18, 2026
88f08fd
job_runs: use maps.Copy in on_file_change hash merge
radakam Aug 18, 2026
8977dcd
job_runs: drop filepath.Glob jargon from on_file_change schema docs
radakam Aug 19, 2026
2db8ec5
acc: refresh on_file_change goldens for resource-key progress lines
radakam Aug 19, 2026
6fd75c8
acc: cover on_file_change globs, errors, and appear/disappear
radakam Aug 19, 2026
2806e12
acc: expand on_file_change glob coverage and skip ignored files
radakam Aug 19, 2026
3e19c16
acc: force-add gitignored on_file_change fixture
radakam Aug 19, 2026
7e9e1a0
acc: drop READPLAN from the on_file_change glob test
radakam Aug 20, 2026
6bdcffc
acc: restore READPLAN on the on_file_change glob test
radakam Aug 20, 2026
5ac0e4b
job_runs: address on_file_change review comments
radakam Aug 20, 2026
0a62f66
job_runs: list every trigger field to satisfy exhaustruct
radakam Aug 20, 2026
5c3bed3
job_runs: keep empty nested lifecycle on state and remote
radakam Aug 21, 2026
31e7286
job_runs: keep the lifecycle fingerprint out of the remote type
radakam Aug 21, 2026
07e6c79
job_runs: store on_file_change hashes on the trigger itself
radakam Aug 24, 2026
580aaea
acc: refresh job_runs goldens for the omitted empty trigger
radakam Aug 24, 2026
3718f1c
job_runs: error when on_file_change hashes nothing
radakam Aug 24, 2026
db83a46
acc: fix on_file_change_errors diagnostic line numbers
radakam Aug 24, 2026
a9f1c7d
acc: removing on_file_change is unchanged, not an update
radakam Aug 24, 2026
f020446
job_runs: resolve on_file_change relative to the defining YAML file
radakam Aug 24, 2026
8501706
job_runs: reject absolute on_file_change patterns
radakam Aug 25, 2026
03c484a
job_runs: warn instead of erroring when on_file_change hashes nothing
radakam Aug 25, 2026
c433182
acc: stop Git Bash rewriting the on_file_change absolute pattern
radakam Aug 25, 2026
b0437a3
acc: read the absolute on_file_change pattern from a committed file
radakam Aug 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .nextchanges/bundles/job-runs-on-file-change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
direct: `resources.job_runs` can set `lifecycle.triggers.on_file_change` to a path or glob under the sync root to re-fire the run when matched file contents change (content hash), or when matches appear or disappear. Removing the trigger does not recreate the existing run.
9 changes: 7 additions & 2 deletions acceptance/bundle/refschema/out.fields.txt
Original file line number Diff line number Diff line change
Expand Up @@ -905,15 +905,18 @@ resources.job_runs.*.jar_params[*] string ALL
resources.job_runs.*.job_id int64 ALL
resources.job_runs.*.job_parameters map[string]string ALL
resources.job_runs.*.job_parameters.* string ALL
resources.job_runs.*.lifecycle *dresources.JobRunLifecycleState STATE
resources.job_runs.*.lifecycle *resources.JobRunLifecycle INPUT
resources.job_runs.*.lifecycle dresources.JobRunLifecycleState STATE
resources.job_runs.*.lifecycle resources.Lifecycle INPUT
resources.job_runs.*.lifecycle.prevent_destroy bool INPUT
resources.job_runs.*.lifecycle.triggers *dresources.JobRunTriggersState STATE
resources.job_runs.*.lifecycle.triggers []resources.JobRunTrigger INPUT
resources.job_runs.*.lifecycle.triggers dresources.JobRunTriggersState STATE
resources.job_runs.*.lifecycle.triggers.on_bundle_deploy string STATE
resources.job_runs.*.lifecycle.triggers.on_file_change map[string]string STATE
resources.job_runs.*.lifecycle.triggers.on_file_change.* string STATE
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.*.modified_status string INPUT
resources.job_runs.*.notebook_params map[string]string ALL
resources.job_runs.*.notebook_params.* string ALL
Expand All @@ -936,6 +939,8 @@ resources.job_runs.*.python_params []string ALL
resources.job_runs.*.python_params[*] string ALL
resources.job_runs.*.queue *jobs.QueueSettings ALL
resources.job_runs.*.queue.enabled bool ALL
resources.job_runs.*.resolved_file_triggers map[string]string INPUT
resources.job_runs.*.resolved_file_triggers.* string INPUT
resources.job_runs.*.resolved_job_id int64 INPUT
resources.job_runs.*.result_state jobs.RunResultState REMOTE STATE
resources.job_runs.*.run_id int64 REMOTE
Expand Down
3 changes: 3 additions & 0 deletions acceptance/bundle/resources/job_runs/failed_run/output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ Files: 7 uploaded, 0 deleted
"new_state": {
"value": {
"job_id": [MY_JOB_ID],
"lifecycle": {
"triggers": {}
},
"result_state": "SUCCESS"
}
},
Expand Down
22 changes: 2 additions & 20 deletions acceptance/bundle/resources/job_runs/on_bundle_deploy/output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,6 @@ Plan: 1 to add, 0 to change, 1 to delete, 1 unchanged

>>> jq .plan["resources.job_runs.my_run"].changes tmp.plan.json
{
"lifecycle": {
"action": "recreate",
"reason": "immutable",
"old": {
"triggers": {
"on_bundle_deploy": "[UUID]"
}
},
"new": {
"triggers": {
"on_bundle_deploy": "[UUID]"
}
}
},
"lifecycle.triggers.on_bundle_deploy": {
"action": "recreate",
"reason": "immutable",
Expand Down Expand Up @@ -108,14 +94,10 @@ Plan: 0 to add, 0 to change, 0 to delete, 2 unchanged

>>> jq .plan["resources.job_runs.my_run"].changes tmp.plan.json
{
"lifecycle": {
"lifecycle.triggers.on_bundle_deploy": {
"action": "skip",
"reason": "trigger removed",
"old": {
"triggers": {
"on_bundle_deploy": "[UUID]"
}
}
"old": "[UUID]"
},
"result_state": {
"action": "skip",
Expand Down
18 changes: 18 additions & 0 deletions acceptance/bundle/resources/job_runs/on_file_change/databricks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
bundle:
name: job-runs-on-file-change

resources:
jobs:
my_job:
name: my-job
tasks:
- task_key: main
notebook_task:
notebook_path: /Workspace/test

job_runs:
my_run:
job_id: ${resources.jobs.my_job.id}
lifecycle:
triggers:
- on_file_change: seed.txt
Comment thread
radakam marked this conversation as resolved.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

207 changes: 207 additions & 0 deletions acceptance/bundle/resources/job_runs/on_file_change/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@

=== first deploy triggers a run
>>> [CLI] bundle deploy
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-on-file-change/default/files...
Output from job_runs.my_run: id=[MY_RUN_ID]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID]?o=[NUMID]
Output from job_runs.my_run: id=[MY_RUN_ID]: SUCCESS
Created job_runs.my_run
Created jobs.my_job
Files: 6 uploaded, 0 deleted
Resources: 2 created, 0 changed, 0 deleted, 0 unchanged

>>> read_id.py my_job
[MY_JOB_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]
}
}

=== redeploy with unchanged file plans nothing
>>> [CLI] bundle plan
Plan: 0 to add, 0 to change, 0 to delete, 2 unchanged

>>> [CLI] bundle deploy
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-on-file-change/default/files...
Files: 2 uploaded, 0 deleted
Resources: 0 created, 0 changed, 0 deleted, 2 unchanged

>>> print_requests.py //jobs/run-now

=== touch without content change plans nothing
>>> [CLI] bundle plan
Plan: 0 to add, 0 to change, 0 to delete, 2 unchanged

>>> [CLI] bundle deploy
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-on-file-change/default/files...
Files: 3 uploaded, 0 deleted
Resources: 0 created, 0 changed, 0 deleted, 2 unchanged

>>> print_requests.py //jobs/run-now

=== editing the file re-fires
>>> [CLI] bundle plan
recreate job_runs.my_run
Comment thread
radakam marked this conversation as resolved.

Plan: 1 to add, 0 to change, 1 to delete, 1 unchanged

>>> jq .plan["resources.job_runs.my_run"].changes tmp.plan.json
{
"lifecycle.triggers.on_file_change": {
"action": "recreate",
"reason": "immutable",
"old": {
Comment thread
radakam marked this conversation as resolved.
"seed.txt": "[FILE_HASH][0]"
},
"new": {
"seed.txt": "[FILE_HASH][1]"
}
},
"lifecycle.triggers.on_file_change['seed.txt']": {
"action": "recreate",
"reason": "immutable",
"old": "[FILE_HASH][0]",
"new": "[FILE_HASH][1]"
},
"result_state": {
"action": "skip",
"reason": "remote_already_set",
"new": "SUCCESS",
"remote": "SUCCESS"
}
}

=== bundle deploy
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-on-file-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=[NUMID]
Output from job_runs.my_run: id=[MY_RUN_ID_2]: SUCCESS
Recreated job_runs.my_run
Files: 4 uploaded, 0 deleted
Resources: 1 created, 0 changed, 1 deleted, 1 unchanged

>>> print_requests.py --keep //jobs/runs/delete
{
"method": "POST",
"path": "/api/2.2/jobs/runs/delete",
"body": {
"run_id": [MY_RUN_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]
}
}

=== deleting the file warns and re-fires
>>> [CLI] bundle plan
Warning: lifecycle.triggers.on_file_change: no files match "seed.txt"
in databricks.yml:18:29

recreate job_runs.my_run

Plan: 1 to add, 0 to change, 1 to delete, 1 unchanged

>>> changes
Warning: lifecycle.triggers.on_file_change: no files match "seed.txt"
in databricks.yml:18:29

{
"lifecycle.triggers.on_file_change": {
"action": "recreate",
"reason": "immutable",
"old": {
"seed.txt": "[FILE_HASH][1]"
},
"new": {
"seed.txt": ""
}
},
"lifecycle.triggers.on_file_change['seed.txt']": {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Non blocking but still a bit weird that have both "lifecycle.triggers.on_file_change['seed.txt']" and "lifecycle.triggers.on_file_change" here.

"action": "recreate",
"reason": "immutable",
"old": "[FILE_HASH][1]",
"new": ""
},
"result_state": {
"action": "skip",
"reason": "remote_already_set",
"new": "SUCCESS",
"remote": "SUCCESS"
}
}

>>> [CLI] bundle deploy
Warning: lifecycle.triggers.on_file_change: no files match "seed.txt"
in databricks.yml:18:29

Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-on-file-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=[NUMID]
Output from job_runs.my_run: id=[MY_RUN_ID_3]: SUCCESS
Recreated job_runs.my_run
Files: 3 uploaded, 1 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]
}
}

=== restoring the file re-fires again
>>> [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/job-runs-on-file-change/default/files...
Output from job_runs.my_run: id=[MY_RUN_ID_4]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID_4]?o=[NUMID]
Output from job_runs.my_run: id=[MY_RUN_ID_4]: SUCCESS
Recreated job_runs.my_run
Files: 3 uploaded, 1 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 on_file_change is unchanged and does not run
>>> [CLI] bundle plan
Plan: 0 to add, 0 to change, 0 to delete, 2 unchanged

>>> [CLI] bundle deploy
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-on-file-change/default/files...
Files: 3 uploaded, 0 deleted
Resources: 0 created, 0 changed, 0 deleted, 2 unchanged

>>> print_requests.py //jobs/run-now

>>> [CLI] bundle destroy --auto-approve
The following resources will be deleted:
delete resources.job_runs.my_run
delete resources.jobs.my_job

All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/job-runs-on-file-change/default

Destroy: 2 deleted
64 changes: 64 additions & 0 deletions acceptance/bundle/resources/job_runs/on_file_change/script
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
cleanup() {
trace $CLI bundle destroy --auto-approve
rm -f out.requests.txt
}
trap cleanup EXIT

changes() {
$CLI bundle plan -o json | jq '.plan["resources.job_runs.my_run"].changes'
}

title "first deploy triggers a run"
trace $CLI bundle deploy
trace read_id.py my_job
# Name the first run so the recreated one becomes [MY_RUN_ID_2].
read_id.py my_run > /dev/null
trace print_requests.py //jobs/run-now

title "redeploy with unchanged file plans nothing"
trace $CLI bundle plan
trace $CLI bundle deploy
trace print_requests.py //jobs/run-now

title "touch without content change plans nothing"
python -c "import os; os.utime('seed.txt', None)"
trace $CLI bundle plan
trace $CLI bundle deploy
trace print_requests.py //jobs/run-now

title "editing the file re-fires"
update_file.py seed.txt "v1" "v2"
trace $CLI bundle plan
# Save the plan so the READPLAN=1 variant deploys the hashes computed here.
# The deploy is not traced: readplanarg makes the command line differ per variant.
$CLI bundle plan -o json > tmp.plan.json
trace jq '.plan["resources.job_runs.my_run"].changes' tmp.plan.json
title "bundle deploy\n"
$CLI bundle deploy $(readplanarg tmp.plan.json)
read_id.py my_run > /dev/null
trace print_requests.py --keep //jobs/runs/delete
trace print_requests.py //jobs/run-now
Comment thread
radakam marked this conversation as resolved.

title "deleting the file warns and re-fires"
mv seed.txt tmp.seed.txt
trace $CLI bundle plan
trace changes
trace $CLI bundle deploy
read_id.py my_run > /dev/null
trace print_requests.py //jobs/run-now

title "restoring the file re-fires again"
mv tmp.seed.txt seed.txt
trace $CLI bundle plan
trace $CLI bundle deploy
read_id.py my_run > /dev/null
trace print_requests.py //jobs/run-now

title "removing on_file_change is unchanged and does not run"
update_file.py databricks.yml " lifecycle:
triggers:
- on_file_change: seed.txt
" ""
trace $CLI bundle plan
trace $CLI bundle deploy
trace print_requests.py //jobs/run-now
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v1
14 changes: 14 additions & 0 deletions acceptance/bundle/resources/job_runs/on_file_change/test.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Deploy both by re-planning and from a plan saved on disk, so the hashes
# computed during planning survive plan serialization.
EnvMatrix.READPLAN = ["", "1"]

Ignore = ["tmp.plan.json", "tmp.seed.txt"]

# Content hashes are stable but unreadable, and the parent's `\d{8,}` rule
# mangles any digit run inside them. Order=1 runs before it; Distinct keeps
# different hashes distinguishable.
[[Repls]]
Old = '[0-9a-f]{64}'
New = '[FILE_HASH]'
Order = 1
Distinct = true
Loading
Loading