Skip to content

ci: fail loudly on stale uv.lock and pin one ruff repo-wide (#280) - #281

Merged
NeuralEmpowerment merged 2 commits into
mainfrom
ci/uv-locked
Jul 29, 2026
Merged

ci: fail loudly on stale uv.lock and pin one ruff repo-wide (#280)#281
NeuralEmpowerment merged 2 commits into
mainfrom
ci/uv-locked

Conversation

@NeuralEmpowerment

Copy link
Copy Markdown
Contributor

Closes #280.

Problem

uv sync ran without --locked, so a stale uv.lock silently re-resolved at run time instead of failing. CI and a developer machine could install different dependency sets for the same commit.

This already cost a debugging cycle on #279: ruff format --check passed locally and failed in CI on the same commit, because each side resolved a different ruff. The symptom looked like a formatting bug in unrelated code.

The unpinned uv tool install ruff was the same defect from the other direction - any new ruff release could turn CI red with no code change, and it already had. On main today, ruff resolves to 0.16.0 for the hook check, which flags 20 BLE001/S110 violations on the plugin hook handlers.

Two locks were stale on main when this branch was cut: lib/python/agentic_logging and tests/consumer_contracts.

Changes

Fail loud on stale locks

  • uv sync --locked at all four call sites (qa.yml x3, build-workspace-images.yml)
  • Regenerated the two stale locks

One ruff for the whole repo

  • RUFF_VERSION: "0.16.0" as a workflow-level env in qa.yml, used by uv tool install ruff==${RUFF_VERSION}
  • ruff==0.16.0 / mypy==2.3.0 pinned exactly in every pyproject.toml that declares them
  • New ruff-version-consistency job asserts every pyproject.toml pins exactly RUFF_VERSION, so the hook check and the per-package checks can never format against different ruff releases. It also fails if it finds zero declarations, so the guard can't silently stop guarding. Wired into qa-success.

Root ruff.toml

  • Hook handlers under plugins/*/hooks/ are exempted from BLE001 and S110. Their fail-open except Exception: pass is required by design - plugin-validate actively enforces that every handler has a top-level exception handler. Without an owning project these files inherited whatever the default rule set of the day happened to be; now that's pinned and the rationale is recorded in the file.

Local parity

  • just python-lock-check and just python-lock-update, wired into just qa and just ci
  • python_qa.py sync now uses --locked too, so the local sync path matches CI

Fallout from moving every package onto ruff 0.16.0

Both verified locally:

  • agentic_events: Recording(str, Enum) -> StrEnum (UP042). 163 tests pass.
  • agentic_logging: README code blocks reformatted.

Verification

Run locally against the branch:

Check Result
uv lock --check (all 6 projects) current
agentic_events: ruff check / format / pytest pass, 163 tests
agentic_isolation: ruff check / format / pytest pass, 436 tests
agentic_logging: ruff / mypy / pytest --cov-fail-under=90 pass, 42 tests, 93.83%
hook files: ruff format + check (CI's exact glob, ruff 0.16.0) pass
hooks unit tests pass, 411 tests
consumer contracts pass, 35 tests

Acceptance criteria from #280, checked by hand:

  • A deliberately stale lock (ruff==0.16.0 -> 0.15.10 in a pyproject) makes uv sync --locked fail with The lockfile at uv.lock needs to be updated, but --locked was provided.
  • The same edit makes the ruff-version-consistency guard fail, naming the offending file and both versions.

Not included

The issue also floats a periodic job that regenerates locks and opens a PR. Left out to keep this reviewable - happy to file a follow-up.

CI ran `uv sync` without `--locked`, so a stale lockfile silently re-resolved
at run time instead of failing. Local machines and CI could install different
dependency sets for the same commit - which is exactly what burned a debugging
cycle on #279, where `ruff format --check` passed locally and failed in CI
because each side resolved a different ruff.

The unpinned `uv tool install ruff` was the same class of defect from the other
direction: any new ruff release could turn CI red with no code change. It
already had - ruff 0.16.0 flags BLE001/S110 on the plugin hook handlers, whose
fail-open `except Exception: pass` is required by design and enforced by the
plugin-validate job.

Changes:
- `uv sync --locked` at all four call sites (qa.yml x3, build-workspace-images.yml)
- Regenerate the stale agentic_logging and consumer_contracts locks
- Pin ruff==0.16.0 and mypy==2.3.0 exactly in every pyproject that declares them
- New `ruff-version-consistency` job: RUFF_VERSION in qa.yml is the single
  source of truth, and every pyproject must pin that exact version
- Root ruff.toml ignoring BLE001/S110 for plugin hook handlers, with the
  rationale recorded
- `just python-lock-check` / `python-lock-update` for local parity, wired into
  `just qa` and `just ci`; `python_qa.py sync` now uses --locked too

Fallout from moving every package onto ruff 0.16.0, all verified locally:
- agentic_events: `Recording(str, Enum)` -> `StrEnum` (UP042)
- agentic_logging: README code blocks reformatted

Closes #280
Copilot AI review requested due to automatic review settings July 29, 2026 19:44

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

The ruff/mypy pin touched plugins/research/tools/firecrawl/pyproject.toml,
which plugin-version-check counts as plugin content. Bumping rather than
dropping the pin: the ruff-version-consistency guard requires every pyproject
that declares ruff to pin RUFF_VERSION, and carving out an exception would
reintroduce the drift this PR closes.
Copilot AI review requested due to automatic review settings July 29, 2026 19:54

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@NeuralEmpowerment
NeuralEmpowerment merged commit 24d8c5a into main Jul 29, 2026
13 checks passed
@NeuralEmpowerment
NeuralEmpowerment deleted the ci/uv-locked branch July 29, 2026 20:02
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.

CI: uv sync without --locked lets lockfiles drift silently (works locally, fails in CI)

2 participants