fix(ci): validate cached dbt e2e environments; pin the uv venv to the system Python

Merged
AN
10d ago
+110-20

Issue for this PR

Closes #1257

Type of change

  • Bug fix (CI)

What does this PR do?

Main's dbt-tools E2E job fails on every run. Both of its caches hold venvs whose bin/python links to an interpreter outside the cached directory, so after a restore bin/dbt exists but does not run:

  • The scenario cache holds a uv venv linked to a uv-managed Python that fresh runners lack: 3 dbt resolver e2e > uv tests fail with ENOENT.
  • The version cache (built 2026-05-20) no longer runs either. Setup prints already installed (unknown), and the multi-version suite finds no working dbt and skips every test with a warning. Main runs 11 E2E tests; with that suite running it is 48.

actions/cache saves only on a miss, so the same broken environments come back on every run.

  • Setup scripts (setup-resolve.sh, setup-versions.sh): a cached environment counts only if its dbt --version succeeds (and, for the version venvs, reports the requested version); otherwise the script prints ↻ <name> cache is stale (dbt does not run) — rebuilding... and rebuilds. Both build on DBT_E2E_PYTHON when set, and the uv venv is created with --python so it links to that interpreter.
  • Versions test (dbt-versions.test.ts): when DBT_E2E_VERSIONS is set (CI sets it), a requested version without a working dbt that reports that version fails the run instead of skipping. Each version's beforeAll now calls configure() with its own venv. The tests never reset the dbt binary dbt-cli caches, so every version after the first ran the first one's dbt; it surfaced as 4 dbt 1.11 failures (a DuckDB file written by 1.11 that 1.10 cannot read).
  • Workflow (ci.yml): both cache keys include the setup-python version, so a new 3.11 patch release starts fresh caches instead of restoring dead venvs; the scenario key also moves to -v2. Both setup steps get DBT_E2E_PYTHON from setup-python. The job also runs on PRs that touch packages/dbt-tools/**, with a read-only token and no persisted credentials, so a fix to its own setup is proven before merge.

How did you verify your code works?

Linux, Python 3.11.15, uv 0.11.1, from packages/dbt-tools with CI's env vars and no dbt on PATH:

  1. Fresh build of dbt 1.8/1.10/1.11 and the venv/uv scenarios, then bun run test:e2e: 48 pass, 0 fail. Without the configure() call: 4 dbt 1.11 failures.
  2. CI's broken state, reproduced by pointing the 1.8 venv's bin/python at a missing interpreter: main's setup script printed already installed (unknown) and main's test ran 0 tests and exited 0. The new test fails; the new setup script rebuilt 1.8 and reused it on the next run. The same holds when 1.8 holds a working 1.10, and for a dbt that prints its version and exits 1.
  3. Same for uv (dangling uv/.venv/bin/python): rebuilt. Also checked on macOS with pyenv Python 3.9: stale uv venv rebuilt, resolver tests 23 pass.
  4. dbt-tools unit tests 82 pass; strict marker check and branding audit pass; workflow YAML parses.

On CI (this PR, Python 3.11.17): the first run missed both new keys, built every environment fresh and saved the caches; a later run restored both, each cached dbt passed the run-and-version check, and nothing was rebuilt. Both runs: 48 pass / 0 fail including the new version check. Caches saved by a PR run are scoped to the PR, so main builds its own on the first push after merge and restores from the second.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

🤖 Generated with Claude Code

https://claude.ai/code/session_0174HUYiaceApqHP7EKMNgnb

Summary by CodeRabbit

  • Tests

    • End-to-end checks run for relevant changes in both pull requests and pushes.
    • Requested dbt versions are checked against available environments, and runs fail when a requested version is unavailable.
    • Cached Python environments are validated before use and rebuilt when cached tools are unusable.
    • Environment setup uses the selected Python interpreter for more reliable results after cache restoration.
  • Chores

    • Updated environment cache versioning.
    • Restricted CI checkout access to read-only permissions.

[!NOTE]
Low Risk
Changes are limited to CI workflow and E2E setup scripts; they do not alter shipped application or resolver runtime behavior in production.

Overview
Fixes dbt-tools E2E failures where a restored GitHub Actions cache looked healthy (.done present, bin/dbt on disk) but dbt exited with ENOENT because the uv venv pointed at a uv-managed Python outside the cached tree.

setup-resolve.sh now treats a cache hit as valid only after dbt --version succeeds (with optional timeout); stale caches are rebuilt with a clear message. uv venvs are created with --python "$REAL_PYTHON", and DBT_E2E_PYTHON lets CI prefer setup-python’s interpreter over the image default. All scenarios share a cached_or_rebuild helper instead of trusting .done alone.

.github/workflows/ci.yml bumps the resolve-env cache key v1 → v2, passes DBT_E2E_PYTHON into setup, and runs dbt-tools E2E on PRs that touch packages/dbt-tools (not only on push to main). The job uses read-only contents and persist-credentials: false on checkout, matching other PR-scoped jobs.

Reviewed by Cursor Bugbot for commit b443456e768c7e1a25b686e49cad59e988a02117. Bugbot is set up for automated code reviews on this repo. Configure here.