Fix #2280: make lint/security auto-sync venv in fresh worktrees - #2293
Conversation
Two coupled changes restore working `make lint*`/`make security` on fresh worktrees (and any checkout without `.venv`): 1. Tool-resolution variables (`RUFF`, `PYTEST`, `MYPY`, `YAMLLINT`, `BANDIT`, `PYTHON`) switch from `:=` (immediate) to `=` (recursive) so the `wildcard $(VENV_BIN)/...)` check re-evaluates each time the variable expands — i.e. *after* a prereq has populated `.venv`. Previously they resolved once at parse time, locking to the bare tool name even after `uv sync` ran. 2. `lint-python`, `lint-yaml`, `lint-custom`, `lint-python-fix`, `lint-yaml-fix`, and `security` gain a `sync-venv-if-uv` prereq — matching the pattern already used by `test`/`test-all`. On dev/CI this auto-runs `uv sync` (fast no-op when in sync). On the sandbox container, where uv isn't installed, it remains a no-op and the recursive variable falls through to the globally-installed tool. Targets that use only system tools (`lint-shell`, `lint-docker`, `lint-actions`, `lint-shell-fix`) are intentionally left alone.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Approval
The fix correctly addresses #2280. The two-part diagnosis is right and the explanation in the PR description nails it: adding the sync-venv-if-uv prereq alone wouldn't have worked because RUFF := … was resolved to bare ruff at Make parse time, before any recipe ran. Switching to = defers wildcard evaluation to recipe-expansion time, after the prereq has populated .venv. I reproduced this locally to confirm.
What I checked
- Recursive evaluation timing: Verified with a minimal Makefile that
RUFF = $(if $(wildcard …),…,fallback)re-evaluates after a prereq creates the file. Works as the PR claims. - Phony prereq dedup with
make lint: GNU Make deduplicates phony prereqs across sibling targets in a single invocation, sosync-venv-if-uvruns once permake linteven though six sub-targets list it. No multiplicativeuv synccost. - Sandbox path:
command -v uvfails in the sandbox →sync-venv-if-uvis a no-op →wildcardreturns empty → falls through to bare tool name on global PATH. End state matches pre-PR behavior. - CI: All
lint.ymlandtest.ymljobs runuv sync --extra devbefore invokingmake. The new prereq is a fast no-op against an already-synced lockfile, so no double-sync cost. - Target-specific PYTHONPATH propagation:
lint-pythonandlint-customsetexport PYTHONPATHand add the new prereq. Target-specific exports propagate to prereqs in GNU Make, sosync-venv-if-uv(and the sub-make inside it) runs withPYTHONPATH=shared:gateway:orchestrator.uv syncdoesn't import project modules, so this is harmless. The same shape is already intest:andtest-all:, so this matches the established pattern. - Targets correctly left alone:
lint-shell,lint-docker,lint-actions,lint-shell-fixuse system tools (shellcheck/shfmt/hadolint/actionlint), not venv-resolved tools — no prereq needed.check-lintersreferences$(RUFF)etc. but is a diagnostic; auto-syncing inside it would mask the very state the user wants to inspect, so excluding it is correct.
Side benefit worth noting
.github/scripts/checks/lint_check.py:41 invokes make lint directly without pre-syncing. Before this PR, that path would have hit the same "ruff: No such file or directory" failure on a fresh checkout. This PR makes that call self-bootstrapping.
Non-blocking observations
-
Pre-existing prereq inconsistency (out of scope; flagging only).
smoketest-long-poll(line 369) andtest-integration/test-e2e/test-security(lines 394/398/402) still depend onvenvrather thansync-venv-if-uv, so they will fail in the sandbox the same waylint-pythondid before this PR. They require Docker/k3s and aren't typically run from the sandbox, so it's mostly moot — but a follow-up unifying the prereq pattern would be nice. -
Test plan checkboxes unchecked. "CI green" and "Sandbox image builds and lints/tests still run there" are still unchecked. The reasoning in the PR description is sound and matches my analysis, but please confirm these green before merging — particularly that the sandbox image build still passes, since the recursive-variable change does subtly alter the timing of
wildcardcalls on every recipe invocation. -
Stat amplification (negligible). Each
$(RUFF)reference in a recipe now performs a freshwildcard(filesystem stat) instead of a single parse-time evaluation. In hot recipes that reference the variable several times, this is a handful of extra stats permakeinvocation — well below the noise floor.
— Authored by egg
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Re-Review: Approved (unchanged)
The delta since my previous review at 57a11c33 is a single merge commit (c5445f9 — Merge branch 'main' into egg/2280-lint-fresh-venv). No new author commits.
What I re-verified
- Makefile is byte-identical to the previously approved revision. The blob SHA of
Makefileat57a11c33and atc5445f9is the same (36e62b7). The merge from main did not touch this file, and there are no merge conflicts to inspect. - PR diff vs
origin/mainis unchanged in scope.git diff origin/main..HEAD --statstill reportsMakefile | 26 ++++++++++++++------------(14 insertions, 12 deletions). This is exactly the surface I reviewed before. - Main commits brought in by the merge are unrelated to this fix.
ca4191d(docs update for tester scaffold-first, #2276) and2f1c1e1(ruff v0.15.12 sync + formatter sweep, #2297). Neither interacts with the recursive-variable /sync-venv-if-uv-prereq logic. In fact, #2297 makes this PR's bootstrapping behavior more valuable on fresh worktrees, since it pins a newer ruff that a stale.venvwould not satisfy.
Status of prior non-blocking observations
Carried over from my previous review — none are blocking, restating only because the PR is open longer now:
- Unchecked test-plan boxes ("CI green", "sandbox image still lints/tests"). Worth a final glance at the latest GitHub Actions run before merge, particularly because the recursive
=change subtly changes whenwildcardis evaluated on every recipe invocation. - Pre-existing
venv-prereq inconsistency onsmoketest-long-poll,test-integration,test-e2e,test-security— out of scope for this PR; flagged as future cleanup only. - Stat amplification from recursive expansion — negligible.
My previous approval stands. Ship it once CI is green.
— Authored by egg
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
Both reviews are
The PR is ready to merge once the human verifies CI is green. — Authored by egg |
| egg is addressing review feedback... |
|
Re-review is
Reviewer's standing instruction: "Ship it once CI is green." — Authored by egg |
|
egg feedback addressed. View run logs 4 previous review(s) hidden. |
Closes #2280.
Summary
RUFF,PYTEST,MYPY,YAMLLINT,BANDIT,PYTHON) from:=(immediate) to=(recursive) so thewildcard $(VENV_BIN)/...)re-evaluates after a prereq has populated.venv. The old behavior locked the variable at parse time, which is why even adding auv syncprereq wasn't enough on its own.sync-venv-if-uvas a prereq tolint-python,lint-yaml,lint-custom,lint-python-fix,lint-yaml-fix, andsecurity— mirroring the pattern already used bytest/test-all.Why this is a two-part fix
I initially tried just (2). The prereq did run
uv syncand populate.venv/bin/ruff, butmake lint-pythonstill failed withmake: ruff: No such file or directorybecauseRUFF :=had already been resolved to bareruffat Make parse time, before any recipe ran. Switching to=makes the wildcard re-evaluate at recipe-expansion time, after the prereq has done its work.Sandbox compatibility
The sandbox container has tools installed globally and no
uv:sync-venv-if-uvis a no-op whenuvisn't onPATH(existing behavior).wildcardreturns empty (no.venv), so it falls through to the bare tool name — found onPATHbecause they're globally installed. Same end state as before.Targets intentionally left alone
lint-shell,lint-docker,lint-actions,lint-shell-fixuse system tools (shellcheck/shfmt/hadolint/actionlint), not venv-resolved tools, so the venv state is irrelevant.Test plan
rm -rf .venv && make lint-python— passes (was the reported failure)rm -rf .venv && make lint— passes end-to-endmake securityworks without manualmake depsfirstuv, globally-installed tools)