Skip to content

job_runs: add lifecycle.triggers.on_file_change - #6309

Open
radakam wants to merge 8 commits into
mainfrom
deco-job-runs-lifecycle-on-file-change
Open

job_runs: add lifecycle.triggers.on_file_change#6309
radakam wants to merge 8 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 globs under the sync root and fingerprint matched files by content hash (mtime-only touch does not re-fire).
  • Re-fire when a hash changes or when matches appear/disappear; removing the trigger is a state-only update (no new run).
  • Reject paths outside the sync root, empty patterns, directory-only matches, and combining with prevent_destroy.

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.

Tests

  • Unit: resolve (glob merge, missing match, path escape, directory-only), validate, PrepareState / OverrideChangeDesc.
  • Acceptance: acceptance/bundle/resources/job_runs/on_file_change — deploy, unchanged redeploy, touch, edit → recreate, clear trigger → update-only.

@eng-dev-ecosystem-bot

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

Copy link
Copy Markdown
Collaborator

Integration test report

Commit: 47994d8

Run: 32248693948

Env 🟨​KNOWN 🔄​flaky 💚​RECOVERED 🙈​SKIP ✅​pass 🙈​skip Time
💚​ aws linux 4 4 286 1162 7:13
💚​ aws windows 4 4 288 1160 7:26
💚​ azure linux 4 4 285 1162 7:16
💚​ azure windows 4 4 287 1160 7:39
🔄​ gcp linux 1 3 4 286 1162 11:16
🟨​ gcp windows 1 1 4 288 1160 9:50
8 interesting tests: 4 SKIP, 3 RECOVERED, 1 KNOWN
Test Name aws linux aws windows azure linux azure windows gcp linux gcp windows
💚​ TestAccept 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
🙈​ TestAccept/bundle/invariant/no_drift 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/vector_search_endpoints/drift/recreated_same_name 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/vector_search_indexes/recreate/embedding_dimension 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/ssh/connection 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🟨​ TestFetchRepositoryInfoAPI_FromRepo 💚​R 💚​R 💚​R 💚​R 🔄​f 🟨​K
💚​ TestFetchRepositoryInfoAPI_FromRepo/root 💚​R 💚​R 💚​R 💚​R 💚​R
💚​ TestFetchRepositoryInfoAPI_FromRepo/subdir 💚​R 💚​R 💚​R 💚​R 💚​R
Top 6 slowest tests (at least 2 minutes):
duration env testname
7:32 azure windows TestAccept
7:28 gcp windows TestAccept
7:17 aws windows TestAccept
3:50 azure linux TestAccept
3:49 aws linux TestAccept
3:44 gcp linux TestAccept

@radakam
radakam marked this pull request as ready for review August 19, 2026 08:04
@github-actions

github-actions Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Approval status: pending

/acceptance/bundle/ - needs approval

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

/bundle/ - needs approval

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

General files (require maintainer)

Files: .nextchanges/bundles/job-runs-on-file-change.md
Based on git history:

  • @janniklasrose -- recent work in bundle/config/mutator/, bundle/schema/, bundle/internal/schema/

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.

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
Re-fire a run when matched file contents change, using stable
hash fingerprints so mtime-only updates (touch) do not recreate.
Satisfy the linter on PrepareState literals after adding OnFileChange,
and refresh out.fields.txt so validate-generated stays green.
Reject patterns outside the sync root and directory-only matches so
the trigger cannot fingerprint escaped paths or silently disarm.
Drop size/mtime fingerprints and the resolve fast path so content
identity alone drives recreate, and touch no longer needs a planner exception.
The modernize linter rejects the manual m[k]=v copy loop, failing CI lint.
@radakam
radakam force-pushed the deco-job-runs-lifecycle-on-file-change branch from 8744e5e to 47994d8 Compare August 19, 2026 11:39
job_id: ${resources.jobs.my_job.id}
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.

do you now have a test for glob? That test should check what happens if one of the file under glob changed or glob set itself changed

Also please add a test for ** even if it does not work, to see that the error is clear.

Also, add an ignored file there -- glob should respect ignores the same way 'sync' does.

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.

Also test something in subdirectory mysubdir/.txt
and in outer directory ../shared/
.txt

Should also test what happens if global matches directories: */*.py


=== editing the file re-fires
>>> [CLI] bundle plan
recreate job_runs.my_run

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.

Can you include json plan here instead? Also add READPLAN variant.

trace $CLI bundle deploy
read_id.py my_run > /dev/null
trace print_requests.py --keep //jobs/runs/delete
trace print_requests.py //jobs/run-now

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.

What about removing the file? and adding it back?

out[filepath.ToSlash(pattern)] = missingFileHash
return out, diags.Append(diag.Diagnostic{
Severity: diag.Warning,
Summary: fmt.Sprintf("lifecycle.triggers.on_file_change: no files match %q", pattern),

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.

This and other errors should have an acceptance test.

}
defer f.Close()

h := sha256.New()

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.

This implementation is fine but it also does way too much potentially.

We could also do this:

  • store stat(x).st_size and stat(x).st_mtime in the state in addition to hash
  • check stat values first:
    = st_size is different? can trigger update without calculating the hash
    = st_size the same and t_mtime is the same? can skip hashing, file was not touched so hash can be assumed the same.

However, let's discuss this first, because we do something similar in file upload so I wonder if we should just re-use the mechanism there.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I tried size+mtime in state in an earlier revision. touch then showed up as local drift, so we needed ignore_local_changes on mtime_ns to prevent it from recreating. Since mtime isn’t part of the recreate condition, putting it in planned state felt less elegant than just hashing the content. I also wouldn’t reuse file upload for this: sync is mtime-only, so a touch would trigger a re-upload and re-fire the job. What do you think?

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.

right, let's keep your implementation it's simple and you only pay for files you track. We can add local optimization later, maybe using cache that we have.

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.

3 participants