Skip to content

air: Announce non-git code_source snapshots before packaging - #7618

Draft
artemy-kolesnikov wants to merge 2 commits into
mainfrom
CNXT-2553-non-git-snapshot-notice
Draft

artemy-kolesnikov wants to merge 2 commits into
mainfrom
CNXT-2553-non-git-snapshot-notice

Conversation

@artemy-kolesnikov

@artemy-kolesnikov artemy-kolesnikov commented Nov 13, 2026 •

Copy link
Copy Markdown

Title: air: Announce non-git code_source snapshots before packaging

Changes

When code_source.root_path is not a git repository, air run falls back to a plain tarball of every non-ignored file in the directory. On main the only output is Uploading <name>.tar.gz..., so a user can ship logs, data or scratch files without noticing (a live run.log was shipped this way in a real workspace). In text mode, air run now prints before the "Staging run artifacts…" spinner starts:

Submitting experiment: snapshot-demo-1
No git repository at /tmp/snapshot-demo/e2e/demo; packaging 4 file(s) (64.2 KiB uncompressed) from the working tree.
Uploading snapshot-demo_1041ca36e38a2235.tar.gz...
  • On a cache hit (unchanged directory), it prints No git repository at <root>; reusing the cached snapshot (N file(s), <size> uncompressed). instead, so no non-git submission is silent.
  • If the shipped files total 64 MiB or more, it adds Tip: limit the snapshot with code_source.include_paths or a .gitignore in the directory. The threshold is snapshotLargeSourceBytes, an arbitrary default that has not been measured and can be tuned. It applies to uncompressed source bytes, because that is what include_paths and .gitignore control. The compressed tarball size stays telemetry-only.
  • Under -o json the notice is skipped, the same way Submitting experiment: is. No JSON fields are added.
  • The count and size cover only what the tarball contains: regular files and symlinks. Symlinks add no bytes. The rule comes from a new tarpack.Archives, which tarpack.Write now uses too, so the two can't drift apart. If some listed paths are skipped, a warning names up to 5 of them, in every output mode: Warning: the snapshot leaves out 1 listed path(s) that are not regular files or symlinks, such as nested git repositories: lib/. --dry-run's would include N file(s) uses the same count.
  • The error from the git repository probe, which isRepository discards, is now logged at debug level (--debug).
  • Git snapshots (git_archive, and plain_tar inside a git repository) print nothing new.

To print before the spinner, uploadSnapshot is split in two. prepareSnapshot resolves the root path and plan, lists the files, names the tarball and checks whether it is already uploaded. preparedSnapshot.upload packages and uploads. submitWorkload runs the first step, then announceSnapshot, before the staging errgroup. Packaging and upload still overlap server-side validation as before. The listing and one Stat request now run before validation starts rather than alongside it.

Why

The Python AIR CLI already prints Non-git directory detected, will create plain tarball without version caching (in the Python AIR CLI's submit path). The Go port never had this notice, and the non-git branch has existed without it since the first Go integration (#5847).

Not addressed here:

  • The raw fatal: not a git repository line in the report is still unexplained, and it does not come from the CLI. Every git subprocess in air run captures its stderr. The only git call against a non-git root_path (rev-parse --is-inside-work-tree) swallows the error, and running both v1.26.0 and main in a non-git directory prints no fatal:. The line came from somewhere else, such as the reporter's shell or harness or remote output relayed by --watch, and this change does not claim to explain it.
  • Follow-up (pre-existing bug, not fixed here): a nested git repository inside a non-git root_path is listed by ls-files --others as a single directory entry. tarpack skips directories, so none of that repository's files ship. This change makes the loss visible (the warning above, and correct counts) but does not include the files. The same gap means a root whose only content is a nested repository gets an empty tarball past the air: refuse to upload an empty code_source snapshot #7570 empty-snapshot check.

Tests

  • cmd/air/snapshot_notice_test.go:
    • Exact notice and reuse text.
    • The 64 MiB tip boundary (64 MiB − 1 B shows no tip; 64 MiB shows it; a cache hit never shows it).
    • shippedSnapshotFiles against tarpack's rule (a directory is skipped, a symlink counts with 0 bytes).
    • The warning's 5-name cap.
    • Nested repo regression: a non-git dir with main.py, run.log and a nested git init'd lib/, run through submitWorkload. The uploaded tarball holds exactly proj/main.py and proj/run.log; the notice says 2 file(s) (19 B uncompressed); the warning names lib/; countSnapshotFiles (dry-run) returns 2.
    • Ordering: submitWorkload with cmdio.NewTestIO, which forces an interactive stderr so the bubbletea spinner really renders. The warning and the notice come before the spinner's first escape sequence and before Uploading. If the announce call is moved inside the staging closure, the test fails 3 runs out of 3.
    • Cache-hit reuse line; showProgress=false (JSON mode) prints no notice but still warns; a git repository gets no notice.
  • New acceptance fixture acceptance/air/run-submit-non-git: a non-git submit with exact count and size (5 file(s) (64.3 KiB uncompressed)), an unchanged resubmit (reuse line), and -o json (no notice; clean envelope).
  • go test ./cmd/air/... ./libs/tarpack/... -count=1 passes. The whole TestAccept/air/ suite passes, including all run-submit* fixtures; existing goldens are unchanged. ./task lint-q-go-root (0 issues), ./task fmt-q-go, ./task deadcode and gofmt are clean.
  • Manually verified against a workspace, from a non-git temp dir with main.py, workload.yaml, run.log and a 64 KB file:
    • --dry-run output is identical with the base and fixed builds (would include 4 file(s)).
    • A real submit with the fixed build printed the notice above (4 files, 64.2 KiB = 15 + 25 + 65536 + 191 B), and the run extracted the tarball on the node.

This pull request and its description were written by Isaac.

When code_source.root_path is not a git repository, `air run` packages
every non-ignored file in the directory and used to print only
"Uploading <name>.tar.gz...". It now says so before the staging spinner
starts, with the number of files and their uncompressed size, or that it
reuses the cached snapshot. At 64 MiB or more it suggests
code_source.include_paths or a .gitignore. The notice is skipped with
-o json, like "Submitting experiment:".

The count and size cover only what the tarball holds (regular files and
symlinks, per tarpack.Archives). Listed paths that tarpack skips, such
as a nested git repository, which ls-files lists as one directory, are
named in a warning; --dry-run no longer counts them. The swallowed
error from the git repository probe is now logged at debug level.

Co-authored-by: Isaac <no-reply@databricks.com>
@github-actions github-actions Bot added the AIR Databricks AI Runtime CLI label Nov 13, 2026
@github-actions

github-actions Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

Commit: e7059e1cb | Workflow run

Regression test report

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

1 tests: regression=1 unreleased=0 coverage=0 failing=0
test branch main (c4559ed) latest (v1.26.0)
TestAccept/air/run-submit-non-git ✅ ❌ ❌
TestAccept/air/run-submit-non-git: branch ✅ | main (c4559ed) ❌ | latest (v1.26.0) ❌

main (c4559ed):

acceptance_test.go:1374: Diff:
        --- air/run-submit-non-git/output.txt
        +++ /tmp/TestAcceptairrun-submit-non-git727996622/002/output.txt
        @@ -3,7 +3,6 @@
         
         >>> [CLI] air run -f run.yaml
         Submitting experiment: submit-non-git-smoke
        -No git repository at [TEST_TMP_DIR]; packaging 5 file(s) (64.3 KiB uncompressed) from the working tree.
         Uploading [SNAPSHOT_TARBALL]...
         Submitted workload with Job Run ID: 555
         View job run at: [DATABRICKS_URL]/jobs/runs/555?o=[WORKSPACE_ID]
        @@ -16,7 +15,6 @@
         
         >>> [CLI] air run -f run.yaml
         Submitting experiment: submit-non-git-smoke
        -No git repository at [TEST_TMP_DIR]; reusing the cached snapshot (5 file(s), 64.3 KiB uncompressed).
         Submitted workload with Job Run ID: 555
         View job run at: [DATABRICKS_URL]/jobs/runs/555?o=[WORKSPACE_ID]
         
        
FAIL

latest (v1.26.0):

acceptance_test.go:1374: Diff:
        --- air/run-submit-non-git/output.txt
        +++ /tmp/TestAcceptairrun-submit-non-git176074120/002/output.txt
        @@ -3,10 +3,9 @@
         
         >>> [CLI] air run -f run.yaml
         Submitting experiment: submit-non-git-smoke
        -No git repository at [TEST_TMP_DIR]; packaging 5 file(s) (64.3 KiB uncompressed) from the working tree.
         Uploading [SNAPSHOT_TARBALL]...
         Submitted workload with Job Run ID: 555
        -View job run at: [DATABRICKS_URL]/jobs/runs/555?o=[WORKSPACE_ID]
        +View job run at: [DATABRICKS_URL]/jobs/runs/555
         
         Tip: use --watch when submitting a run to stream logs to your terminal.
         Stream logs after submission using:
        @@ -16,9 +13,8 @@
         
         >>> [CLI] air run -f run.yaml
         Submitting experiment: submit-non-git-smoke
        -No git repository at [TEST_TMP_DIR]; reusing the cached snapshot (5 file(s), 64.3 KiB uncompressed).
         Submitted workload with Job Run ID: 555
        -View job run at: [DATABRICKS_URL]/jobs/runs/555?o=[WORKSPACE_ID]
        +View job run at: [DATABRICKS_URL]/jobs/runs/555
         
         Tip: use --watch when submitting a run to stream logs to your terminal.
         Stream logs after submission using:
        @@ -33,6 +28,6 @@
           "data": {
             "status": "PENDING",
             "run_id": "555",
        -    "dashboard_url": "[DATABRICKS_URL]/jobs/runs/555?o=[WORKSPACE_ID]"
        +    "dashboard_url": "[DATABRICKS_URL]/jobs/runs/555"
           }
         }
        
FAIL

@eng-dev-ecosystem-bot

eng-dev-ecosystem-bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Collaborator

✅ Integration test report

Commit: e7059e1

Run: 43529784299

Env 💚​RECOVERED ✅​pass 🙈​skip Time
💚​ aws linux-2core-8gb 1 270 16 5:36
💚​ aws-windows-latest-4core-16gb 1 272 14 3:59
💚​ azure linux-2core-8gb 1 269 16 4:43
💚​ azure-windows-latest-4core-16gb 1 271 14 3:54
💚​ gcp linux-2core-8gb 1 270 16 4:38
💚​ gcp-windows-latest-4core-16gb 1 272 14 4:50
Test Name aws linux-2core-8gb aws-windows-latest-4core-16gb azure linux-2core-8gb azure-windows-latest-4core-16gb gcp linux-2core-8gb gcp-windows-latest-4core-16gb
💚​ TestAccept 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
Top 3 slowest tests (at least 2 minutes):
duration env testname
3:26 aws-windows-latest-4core-16gb TestAccept
3:26 gcp-windows-latest-4core-16gb TestAccept
3:25 azure-windows-latest-4core-16gb TestAccept

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

AIR Databricks AI Runtime CLI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants