job_runs: add lifecycle.triggers.on_file_change - #6309
Merged
Merged
Conversation
Collaborator
Integration test reportCommit: f964f6f
Top 3 slowest tests (at least 2 minutes):
|
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
force-pushed
the
deco-job-runs-lifecycle-on-file-change
branch
from
August 19, 2026 09:47
b8c20f6 to
8744e5e
Compare
radakam
force-pushed
the
deco-job-runs-lifecycle-on-file-change
branch
from
August 19, 2026 11:39
8744e5e to
47994d8
Compare
denik
reviewed
Aug 19, 2026
denik
reviewed
Aug 19, 2026
denik
reviewed
Aug 20, 2026
denik
reviewed
Aug 20, 2026
denik
reviewed
Aug 21, 2026
denik
reviewed
Aug 21, 2026
radakam
force-pushed
the
deco-job-runs-lifecycle-on-file-change
branch
from
August 24, 2026 13:24
0756969 to
4e7b960
Compare
denik
reviewed
Aug 24, 2026
radakam
force-pushed
the
deco-job-runs-lifecycle-on-file-change
branch
from
August 25, 2026 08:12
17857f9 to
45c4ccd
Compare
radakam
changed the base branch from
main
to
job-runs-drop-unit-tests-covered-by-acc
August 25, 2026 08:13
Base automatically changed from
job-runs-drop-unit-tests-covered-by-acc
to
main
August 25, 2026 10:05
radakam
force-pushed
the
deco-job-runs-lifecycle-on-file-change
branch
from
August 25, 2026 10:12
d65e640 to
b0437a3
Compare
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)
denik
reviewed
Aug 27, 2026
denik
force-pushed
the
deco-job-runs-lifecycle-on-file-change
branch
3 times, most recently
from
September 4, 2026 10:05
0ad6021 to
5e8ec14
Compare
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.
…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
force-pushed
the
deco-job-runs-lifecycle-on-file-change
branch
from
September 10, 2026 12:52
24f9f1f to
6391342
Compare
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.
| @@ -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)) | |||
Member
There was a problem hiding this comment.
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", |
denik
added a commit
that referenced
this pull request
Sep 13, 2026
…ments Two gaps exposed by job_runs.lifecycle.triggers_state (added in #6309): 1. skippedByTag only returned true when the LAST path segment was internal. A path like lifecycle.triggers_state.on_bundle_deploy was not skipped because "on_bundle_deploy" itself is not internal — "triggers_state" is. Any path through an internal field is also unreachable, so remove the len check. 2. flatten used FindStructFieldByKeyType (which skips internal fields) to look up the type of each path segment for map-vs-struct bracket notation. When the segment is internal that lookup returned nil, so the next level defaulted to dot notation and the map key "k" in on_file_change ended up as .k instead of ['k']. Added rawFieldType as a fallback that finds the Go type without respecting bundle tags.
denik
pushed a commit
that referenced
this pull request
Sep 14, 2026
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
state key (SnapshotJobRunValueTriggers, before variable resolution).
- Resolve variables and workspace fields at initialize time; leave
${resources.*} for the planner so the deploy graph still depends on them.
- Store resolved values as SHA-256 hashes in lifecycle.triggers_state.on_value_change,
a map shared with the on_file_change fingerprints.
- Adding or changing a watch recreates the run; removing one drops the change and
keeps the run, matching on_file_change.
Rebased onto main after on_file_change (#6309) landed and reconciled with its
review lessons: no PersistState (removal is deployplan.ReasonDrop, state keeps
the last fingerprint), OverrideChangeDesc matches paths via path.String() and
shares the file/value cases, prevent_destroy is left to the generic
checkForPreventDestroy rather than a special-case validation, and the pydabs and
refschema artifacts are regenerated.
Co-authored-by: Isaac
denik
pushed a commit
that referenced
this pull request
Sep 14, 2026
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
janniklasrose
pushed a commit
that referenced
this pull request
Sep 15, 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 #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)
janniklasrose
pushed a commit
that referenced
this pull request
Sep 15, 2026
## Changes
Add `lifecycle.triggers.on_file_change` for `resources.job_runs` (direct
engine).
```yaml
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_glob` — `migrations/*` 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.
---------
Co-authored-by: Denis Bilenko <denis.bilenko@databricks.com>
deco-sdk-tagging Bot
added a commit
that referenced
this pull request
Sep 16, 2026
## Release v1.17.0 ### Notable Changes * Bump the direct deployment state version to 3. Clients older than v1.8.0 will reject bundles deployed with this release. ([#6713](#6713)) ### CLI * Add an `INVALID_REFRESH_TOKEN` error code to `databricks auth token --output json` failures. ([#6684](#6684)) * Add experimental `databricks auth docker configure` to configure Docker credential helper access for Databricks Artifact Registry. ([#6700](#6700)) * Add experimental `databricks auth docker token` to generate Docker credentials for Databricks Artifact Registry. ([#6699](#6699)) * `databricks environments setup-local` now reports the `E_PROVISION_CONFLICT` error code instead of the generic `E_PROVISION` when `uv sync` fails to resolve a dependency conflict. ([#6666](#6666)) * Preserve SSH sessions across temporary tunnel disconnects, with bounded replay and backpressure for large transfers. ([#6650](#6650)) * Allow OAuth U2M logins to override the CLI client ID with `--client-id`, profile `client_id`, or `DATABRICKS_CLIENT_ID`. ([#6594](#6594)) ### Bundles * direct: Store a dashboard's `serialized_dashboard` in state as a content hash instead of its full contents. ([#6105](#6105)) * direct: Fix pipelines recreation when the whole `ingestion_definition` block is added or removed. ([#6589](#6589)) * `bundle plan`, `deploy`, and `destroy` no longer report removing `permissions`, `grants`, or secret scope ACLs from a bundle as a deletion, since it leaves the resource untouched. ([#6647](#6647)) * `bundle plan` and `deploy` no longer list or count a resource that was already deleted remotely as a deletion, matching `bundle destroy`; applying still cleans up its stale state entry. ([#6675](#6675)) * Fix `bundle run` failing with `expected an int, found a string` when an unrelated resource references another resource that is not deployed. `bundle run` now resolves `${resources.*}` references only within the resource being run. ([#6690](#6690)) * Add grants support for the AI Gateway `model_service`, `mcp_service`, and `model_provider_service` resources (direct engine). ([#6635](#6635)) * Add bundle support for the AI Gateway `mcp_service` resource (direct engine). ([#6633](#6633)) * Add bundle support for the AI Gateway `model_provider_service` resource (direct engine). ([#6634](#6634)) * Add bundle support for the AI Gateway `model_service` resource (direct engine). ([#6525](#6525)) * Prevent resource drift on catalogs if `storage_root` contained a trailing slash in the URL. ([#6622](#6622)) * Fixed a "lineage mismatch in state files" error that could occur after destroying a bundle and redeploying it from another machine. `bundle destroy` now removes the local state file so no stale lineage is left behind, and prunes the state directories it leaves empty (such as `.internal/` and `sync-snapshots/`). ([#6210](#6210), [#6685](#6685)) * direct: `bundle plan` no longer reports a permanent update on a cluster that uses a cluster policy: when the cluster spec sets `policy_id`, a field present in the remote but absent from the bundle config is not treated as drift. ([#6531](#6531)) * `bundle deploy` on the direct engine now reports each resource as soon as it is deployed, instead of listing them all after the deployment finishes. A deploy that fails part way through now reports the resources it did apply. ([#6361](#6361)) * Direct-engine bundles no longer flag phantom drift on server-populated nested fields under reused config types (e.g. `external_locations` file-event-queue resource IDs, `database_instances` parent-instance refs, `apps` git credential ID). ([#6618](#6618)) * `databricks bundle generate app` now reproduces a git-backed app's `git_repository` and `git_source` configuration instead of emitting a workspace `source_code_path`, so generating from a Git-deployed app no longer silently converts it to workspace source. ([#6656](#6656)) * Improved configuration load time for bundles with many included files. ([#6195](#6195)) * `bundle destroy` no longer deletes triggered job runs, leaving them untouched on the backend. ([#6672](#6672)) * 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](#6309)) * Bundle summary now shows a name for Postgres branches, endpoints, databases, and roles instead of a blank Name field. ([#6663](#6663)) * Added PyDABs (Python) support for cluster policies, dashboards, and Genie spaces. ([#6585](#6585)) * CLI commands no longer imply that a resource whose type has no workspace URL is merely not deployed yet. ([#6583](#6583)) * Capture the implicit dependency a vector search index has on a catalog or schema defined in the same bundle, so the catalog and schema are deployed first. ([#6655](#6655)) ### Dependency Updates * Bump dependencies with known vulnerabilities. ([#6695](#6695)) * Bump `github.com/databricks/databricks-sdk-go` from v0.177.0 to v0.178.0. ([#6673](#6673)) * Bump Terraform provider from v1.131.0 to v1.132.0. ([#6671](#6671))
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Changes
Add
lifecycle.triggers.on_file_changeforresources.job_runs(direct engine).bundle plan/bundle validate) and after predeploy (forbundle deploy). Hash through the sameSyncRootVFS used by sync, so WSFS notebooks and generated overlays are included. An mtime-onlytouchdoes not re-fire.libs/sync.FileList+path.Match), including sync's generated include patterns..gitignoreandsync.excludeare honored. Directories never appear in that list, somigrations/*hashes only regular files.lifecycle.triggers_stateas normalized-pattern → aggregate-hash entries, plus a UUID foron_bundle_deploy. Each aggregate hashes the sorted matched paths and their content hashes. Unarmed runs omitlifecyclefrom state. A no-match pattern stores the empty-set hash under the same pattern key used once files appear.**(path.Matchtreats it as two ordinary stars, not recursive), and combining a trigger withprevent_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
SyncRootkeeps 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 YAMLtriggerslist, 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
SyncRootoverlay reads, absolute-pattern rejection, and sync-root-relative missing-pattern keys; trigger validation for empty and invalid entries andprevent_destroyconflicts; state-only classification when a trigger is removed; omission of emptylifecyclefrom serialized state.on_file_change— first deploy fires; unchanged redeploy andtouchplan nothing; an edit recreates (including READPLAN); deleting and restoring the file each re-fire; removing the trigger performs norun-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_glob—migrations/*plusmysubdir/*.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.