Skip to content

job_runs: add lifecycle.triggers.on_file_change - #6309

Merged
denik merged 60 commits into
mainfrom
deco-job-runs-lifecycle-on-file-change
Sep 11, 2026
Merged

job_runs: add lifecycle.triggers.on_file_change#6309
denik merged 60 commits into
mainfrom
deco-job-runs-lifecycle-on-file-change

Conversation

@radakam

@radakam radakam commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Changes

Add lifecycle.triggers.on_file_change for resources.job_runs (direct engine).

resources:
  job_runs:
    migrate:
      job_id: ${resources.jobs.migrate.id}
      lifecycle:
        triggers:
          - on_file_change: migrations/*.sql
  • Resolve the path or glob relative to the defining YAML file and require the result to stay under the sync root.
  • Fingerprint after build (for bundle plan / bundle validate) and after predeploy (for bundle deploy). Hash through the same SyncRoot VFS used by sync, so WSFS notebooks and generated overlays are included. An mtime-only touch does not re-fire.
  • Match against the files sync would upload (libs/sync.FileList + path.Match), including sync's generated include patterns. .gitignore and sync.exclude are honored. Directories never appear in that list, so migrations/* hashes only regular files.
  • Persist internal lifecycle.triggers_state as normalized-pattern → aggregate-hash entries, plus a UUID for on_bundle_deploy. Each aggregate hashes the sorted matched paths and their content hashes. Unarmed runs omit lifecycle from state. A no-match pattern stores the empty-set hash under the same pattern key used once files appear.
  • Re-fire when an aggregate hash changes or when matches appear or disappear. Removing a trigger leaves the existing run alone and writes a state-only update that clears the fingerprint (no Jobs API call). Re-adding the trigger therefore arms and fires it again without requiring an intervening file edit. A removed file still recreates the run.
  • Reject absolute patterns, patterns outside the sync root, empty patterns, ** (path.Match treats it as two ordinary stars, not recursive), and combining a trigger with prevent_destroy. Warn when no synced files match; the empty-set fingerprint still re-arms once a matching file appears. Pattern errors are reported at validate time; hashing runs only when planning or deploying.

Why

Users need to re-run a job when local inputs change (SQL migrations, notebooks, configs) without re-firing on every deploy. Content hashes keep the trigger stable under sync noise while still catching real edits.

Fingerprints must be computed after build and predeploy scripts, otherwise a trigger on a generated file fires one deploy late. Hashing through SyncRoot keeps notebook and overlay content consistent with what sync uploads.

A pattern that hashes nothing used to look like an unarmed trigger. Recording an empty-set fingerprint under a stable pattern key makes those cases re-arm later, so they are warnings rather than deploy failures.

Fingerprints live on triggers_state, not on the YAML triggers list, so user config and planner state stay distinct. Clearing a trigger must persist that omission; otherwise re-adding it looks unchanged and never fires.

Tests

  • Unit: aggregate content hashing, SyncRoot overlay reads, absolute-pattern rejection, and sync-root-relative missing-pattern keys; trigger validation for empty and invalid entries and prevent_destroy conflicts; state-only classification when a trigger is removed; omission of empty lifecycle from serialized state.
  • Acceptance:
    • on_file_change — first deploy fires; unchanged redeploy and touch plan nothing; an edit recreates (including READPLAN); deleting and restoring the file each re-fire; removing the trigger performs no run-now, clears its state through both live and serialized plans, and re-adding it re-fires.
    • on_file_change_build — a prebuild script rewriting a watched file re-fires the run in that same deploy.
    • on_file_change_globmigrations/* plus mysubdir/*.txt; matches appearing and disappearing re-fire; files below the glob and gitignored files do not; ** is rejected; READPLAN.
    • on_file_change_errors — pattern outside the sync root (error); directory-only, directory plus sync-excluded, only sync-excluded, and no-match cases (warnings); unsupported ** and an absolute pattern (errors).
    • on_file_change_include_sync_root — patterns from included YAML resolve next to the include, at the bundle root (../), and at the sync root (../../); each edit recreates only the corresponding pattern entry.

@eng-dev-ecosystem-bot

eng-dev-ecosystem-bot commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Integration test report

Commit: f964f6f

Run: 34575944054

Env 💚​RECOVERED ✅​pass 🙈​skip Time
💚​ aws linux 1 275 23 4:38
💚​ aws windows 1 277 21 5:05
💚​ azure linux 1 274 23 4:06
💚​ azure windows 1 276 21 5:01
💚​ gcp linux 1 275 23 4:09
💚​ gcp windows 1 277 21 5:00
Test Name aws linux aws windows azure linux azure windows gcp linux gcp windows
💚​ TestAccept 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
Top 3 slowest tests (at least 2 minutes):
duration env testname
5:02 aws windows TestAccept
4:58 azure windows TestAccept
4:58 gcp windows TestAccept

@radakam
radakam marked this pull request as ready for review August 19, 2026 08:04
Base automatically changed from deco-job-runs-lifecycle-on-bundle-deploy to main August 19, 2026 09:41
@radakam
radakam force-pushed the deco-job-runs-lifecycle-on-file-change branch from b8c20f6 to 8744e5e Compare August 19, 2026 09:47
@radakam
radakam requested a review from denik August 19, 2026 11:06
@radakam
radakam force-pushed the deco-job-runs-lifecycle-on-file-change branch from 8744e5e to 47994d8 Compare August 19, 2026 11:39
Comment thread acceptance/bundle/resources/job_runs/on_file_change/databricks.yml
Comment thread acceptance/bundle/resources/job_runs/on_file_change/output.txt Outdated
Comment thread acceptance/bundle/resources/job_runs/on_file_change/script
Comment thread bundle/config/mutator/resolve_job_run_file_triggers.go Outdated
Comment thread bundle/config/mutator/resolve_job_run_file_triggers.go
Comment thread acceptance/bundle/resources/job_runs/on_file_change/output.txt Outdated
@radakam
radakam requested a review from denik August 20, 2026 08:12
Comment thread acceptance/bundle/resources/job_runs/on_file_change_glob/test.toml
@radakam
radakam requested a review from denik August 20, 2026 09:27
Comment thread acceptance/bundle/resources/job_runs/on_file_change/output.txt Outdated
Comment thread acceptance/bundle/resources/job_runs/on_file_change_glob/output.txt Outdated
Comment thread bundle/config/mutator/resolve_job_run_file_triggers_test.go
Comment thread bundle/direct/dresources/job_run.go Outdated
Comment thread bundle/direct/dresources/job_run_test.go Outdated
@radakam
radakam requested a review from denik August 20, 2026 14:10
Comment thread bundle/direct/dresources/job_run.go Outdated
Comment thread bundle/direct/dresources/job_run.go Outdated
@radakam
radakam requested a review from denik August 24, 2026 08:50
@radakam
radakam force-pushed the deco-job-runs-lifecycle-on-file-change branch from 0756969 to 4e7b960 Compare August 24, 2026 13:24
Comment thread acceptance/bundle/resources/job_runs/on_file_change/output.txt Outdated
Comment thread bundle/direct/dresources/job_run.go Outdated
Comment thread bundle/config/mutator/resolve_job_run_file_triggers.go Outdated
Comment thread bundle/config/mutator/resolve_job_run_file_triggers.go Outdated
Comment thread bundle/config/mutator/resolve_job_run_file_triggers.go Outdated
Comment thread bundle/config/mutator/resolve_job_run_file_triggers.go Outdated
Comment thread bundle/config/mutator/resolve_job_run_file_triggers.go Outdated
@radakam
radakam force-pushed the deco-job-runs-lifecycle-on-file-change branch from 17857f9 to 45c4ccd Compare August 25, 2026 08:12
@radakam
radakam changed the base branch from main to job-runs-drop-unit-tests-covered-by-acc August 25, 2026 08:13
@radakam
radakam requested a review from denik August 25, 2026 08:32
Base automatically changed from job-runs-drop-unit-tests-covered-by-acc to main August 25, 2026 10:05
@radakam
radakam force-pushed the deco-job-runs-lifecycle-on-file-change branch from d65e640 to b0437a3 Compare August 25, 2026 10:12
jariaspdev-lab pushed a commit to jariaspdev-lab/cli that referenced this pull request Aug 25, 2026
## Changes
Drop `job_run` unit tests that duplicate existing acceptance coverage,
and add `destroy_unfinished_run` so cancel-before-delete is asserted
through the same path a user destroy takes.

Kept the wait/GetRun stubs that acceptance cannot reach without faking
the Jobs API.

## Why
Split from review on databricks#6309: if a unit test is already covered by acc,
keep the acc test.

## Tests
- New acc: `acceptance/bundle/resources/job_runs/destroy_unfinished_run`
(faulted wait, then destroy cancel + delete order)
Comment thread bundle/direct/dresources/job_run.go Outdated
Comment thread acceptance/bundle/refschema/out.fields.txt Outdated
Comment thread acceptance/bundle/resources/job_runs/on_file_change_include_sync_root/script Outdated
@radakam
radakam requested a review from denik August 27, 2026 16:13
@denik
denik force-pushed the deco-job-runs-lifecycle-on-file-change branch 3 times, most recently from 0ad6021 to 5e8ec14 Compare September 4, 2026 10:05
syncableRelPaths re-derived HasOnFileChange over the job_runs map purely so the
mutator stayed cheap to call from more than one place. With a single call site,
collect the matching names once and list the sync tree only when that set is
non-empty. Sorting the names also keeps diagnostics from several job_runs out of
Go map order.

Co-authored-by: Isaac
…state

Removing a trigger downgraded the change to skip and wrote the cleared
fingerprint, so re-adding the trigger re-fired the run even when the watched
file had not changed since it last ran. Drop the change instead: state keeps
the last hash, and re-adding compares against it, so the run fires only when
the file changed while the trigger was disarmed.

That removes the only producer of PersistState, so the plan entry field, the
change field and the persist-state branches in plan and apply all go away.
lifecycle.triggers state no longer needs anything from the shared engine:
deployplan, bundle_plan.go and bundle_apply.go are back to their state on main,
and skip once again always means no state write.

Co-authored-by: Isaac
…ide table

The knownMissingInRemoteType entry for job_runs was moved and its comment
reworded for no reason; the map is not sorted either way, so put it back
untouched. Only the knownMissingInStateType path has to change: state renamed
triggers to triggers_state, so the whole lifecycle.triggers subtree is now
missing from StateType rather than just its elements.

Write each TestJobRunOverrideChangeDescTriggerRemoved case one field per line
with named fields.

Co-authored-by: Isaac
Keep the assertion at each call site and hoist only the expected substring,
which is the one literal inlining would have repeated five times.

Co-authored-by: Isaac
This reverts 364ff57. plan_no_changes was not the helper meant to be inlined.

Co-authored-by: Isaac
The script writes out.plan.create.json, so the file has to be checked in: with
it missing the run fails with "Test produced unexpected files".

Co-authored-by: Isaac
Build each diagnostic at its call site so severity and locations are visible
there, keeping only the shared summary prefix as a constant.

Co-authored-by: Isaac
filepath.IsAbs only recognises the host's flavour, so "C:\watched.txt" was
rejected on Windows but treated as a relative "C:" directory on macOS and
Linux, where it then warned about matching no files. The same bundle now fails
validation the same way wherever it is deployed from.

Co-authored-by: Isaac
The emptiness rule enumerates every field of the struct, so keep it next to the
struct rather than in PrepareState in another package: a fingerprint added later
without extending it would be silently dropped instead of persisted.

Co-authored-by: Isaac
sync drops files whose notebook type it cannot determine, so a malformed .ipynb
is hashed but never uploaded. That is intended - editing one is when the run
should re-fire - but it is worth saying so where the set is chosen.

Co-authored-by: Isaac
The four fixed cases were spelled as parsed-path Len()+HasPrefix comparisons,
which is exact equality written the long way; every other resource's
OverrideChangeDesc switches on path.String(). Do the same and drop the four
package-level path vars. The single on_file_change pattern entry has no fixed
string, so it stays a Parent() check in the default branch.

Co-authored-by: Isaac
lifecycle.go is for lifecycle settings common to all resources; the job-run
trigger types belong next to JobRun. Pure move, no behaviour change.

Co-authored-by: Isaac
main now autogenerates the job_runs pydabs models, so on_file_change has to be
regenerated into job_run_trigger.py and the refschema golden. Also update the
knownMissingInStateType entry: state renamed triggers to triggers_state, so the
whole lifecycle.triggers subtree is missing from StateType, not just its elements.

Co-authored-by: Isaac
main added a [PLAN_VERSION] replacement, so the committed out.plan.create.json
needs the masked value in place of the literal.

Co-authored-by: Isaac
Rebasing onto main pulled in DMS state recording (#6094), which stamps a
version_id into the deployment block under DMS. out.plan.create.json served
double duty as both the compared golden and the --plan deploy input, so it
could not simply be masked: deploy from the raw plan (keeps version_id) and
nostamp a separate copy for the golden. Also regenerate the refschema for the
on_file_change / triggers_state fields.
sync force-includes only the internal dir; the AI Runtime sync overlay was
replaced by a tgz artifact on main (#6494, #6532).
…neric check

A fired trigger recreates the run, and checkForPreventDestroy already rejects a
recreate on a prevent_destroy resource generically (bundle/phases/plan.go), so
the special-case validation and its schema-doc note were redundant. Removing
them lets the combo create once and error only when a re-fire actually plans a
recreate, matching every other recreate field.
…comment

The AI Runtime sync overlay is gone (tgz artifact now); build and the predeploy
script are the only steps that produce a watched file here.
Write the watched file to disk and hash it directly instead of layering an
in-memory overlay over the sync root.
…troy note

validate-generated failed because the pydabs job_run_trigger.py docstrings still
carried "Incompatible with lifecycle.prevent_destroy"; only annotations.yml and
jsonschema.json had been regenerated.
@denik
denik force-pushed the deco-job-runs-lifecycle-on-file-change branch from 24f9f1f to 6391342 Compare September 10, 2026 12:52
Drop the hand-rolled isWindowsAbs cross-OS check: it rejected "c:foo" and "\foo"
which are valid relative filenames on POSIX, with a misleading "must be relative"
error. filepath.IsAbs is already OS-aware, so a path rooted only on the other OS
falls through to the sync-root containment check, which is what actually guards
against escaping the tree. Reported by shreyas-goenka.
@denik
denik enabled auto-merge September 11, 2026 07:53
@denik
denik added this pull request to the merge queue Sep 11, 2026
Merged via the queue into main with commit 6ad41e9 Sep 11, 2026
35 checks passed
@denik
denik deleted the deco-job-runs-lifecycle-on-file-change branch September 11, 2026 08:23
@@ -0,0 +1 @@
* direct: resources.job\_runs: new lifecycle.triggers.on\_file\_change setting to restart the run when monitored files change. Can be set to a series of paths or globs. ([#6309](https://github.com/databricks/cli/pull/6309))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
* direct: resources.job\_runs: new lifecycle.triggers.on\_file\_change setting to restart the run when monitored files change. Can be set to a series of paths or globs. ([#6309](https://github.com/databricks/cli/pull/6309))
* direct: `resources.job_runs`: new `lifecycle.triggers.on_file_change` setting to restart the run when monitored files change. Can be set to a series of paths or globs. ([#6309](https://github.com/databricks/cli/pull/6309))

Comment on lines +63 to +65
`C:\watched.txt`,
"C:/watched.txt",
"c:watched.txt",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

also test D:?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants