Fix: unbreak the permanently-failing update-constraints pre-commit hook - #56
Open
AmaadMartin wants to merge 6 commits into
Open
Fix: unbreak the permanently-failing update-constraints pre-commit hook#56AmaadMartin wants to merge 6 commits into
AmaadMartin wants to merge 6 commits into
Conversation
This was referenced Jul 30, 2026
added 6 commits
August 1, 2026 07:00
`--check` could never pass, even on files generated seconds earlier. The committed file was handed to uv as `--constraint`, so uv recorded it as a resolution source and injected a `# -c constraints-<ver>.txt.stable.tmp` line into the `# via` block of every constrained package. The committed files carry no such annotations, so the diff was non-empty forever. Seed the candidate with a copy of the committed file instead: uv reads an existing output file as version *preferences*, which keeps pins just as stable without polluting the annotations. That also makes the two-attempt "retry without constraints" ladder dead code -- preferences can never make resolution fail -- so it is removed along with its misleading "Resolution failed even without constraints" message.
README documents `pip install google-adk -c constraints-3.10.txt`, but no constraints file has ever been committed, so the documented flow 404s and `./scripts/update_constraints.sh --check` reports all five as missing. Generated with ./scripts/update_constraints.sh (never by hand: `--check` parses the `--exclude-newer` date back out of the header the script reconstructs). codespell has to skip them: it flags `astroid`, a real pylint dependency, as a misspelling of `asteroid`, which would turn the lint gate red again under a different hook. The existing skip rationale -- generated files, not prose we own -- already covers them.
The CI lint job runs `pre-commit run --all-files`, and the hook's `files:` regex matches `pyproject.toml`, so the constraints updater fired on every pull request regardless of what it touched. That job installs no uv and the script resolves five dependency graphs against PyPI, so the gate was both slow and permanently red -- pre-commit is meant to be a fast, offline formatting pass. Relocate the check to its own workflow that installs uv and only triggers when its actual inputs change, and run it in `--check` mode so it reports drift instead of rewriting files.
The README pointed `curl` at the GitHub blob page, which serves an HTML document rather than the constraints file, so the downloaded file could never be passed to `pip -c`.
Pins the invariants whose absence kept the lint gate red: a constraints file exists for every interpreter the script generates, its header is the one the script reconstructs (so `--check` can read the `--exclude-newer` date back out), no `-c ...tmp` annotation leaked in from the old `--constraint` handling, and pre-commit no longer shells out to the network-dependent update script.
Rebasing onto main picked up langgraph>=1.0.10,<2, langgraph-checkpoint >=4.1.1,<5 and google-genai>=2.12.1, which the previously generated files predate: they pin langgraph==0.4.7, so `uv pip install -c constraints-<ver>.txt` resolves to nothing and `./scripts/update_constraints.sh --check` reports permanent drift. Regenerated with the script, not hand-edited. langgraph moves 0.4.7 -> 1.2.10.
AmaadMartin
force-pushed
the
fix/update-constraints-precommit-hook
branch
from
August 1, 2026 14:08
1aa5450 to
7f10983
Compare
This was referenced Aug 1, 2026
Open
This was referenced Aug 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Please ensure you have read the contribution guide before creating a pull request.
Link to Issue or Description of Change
Closes: #issue_number
Related: #issue_number
Problem: Every pull request fails the Pre-commit Linter job regardless of what it
changes, and the same failure reproduces locally for anyone who follows
CONTRIBUTING.mdand runs
pre-commit run --all-files. Three independent defects each keep it red:The hook always fires and its inputs do not exist.
pre-commit/actionrunspre-commit run --all-files, so the hook'sfiles: ^(pyproject\.toml|constraints-.*\.txt)$regex is matched against the whole repo listing —
pyproject.tomlalways matches. Noconstraints-*.txtfile has ever been committed (git log --diff-filter=A -- 'constraints*.txt'is empty), so
diff -u "$TARGET_FILE" "$NEW_FILE"runs against a nonexistent path, thescript writes the file, and exits
1.The hook runs in update mode, which is non-deterministic by design. The
entry:omits
--check, soEXCLUDE_NEWER_DATE=$(date -d "4 days ago" ...)is recomputed onevery run and baked into the file header. Committing the files alone would fix CI for at
most one day. Only
--checkis date-stable — it re-reads the date out of the committedheader.
Root cause: the script was structurally non-idempotent, so even
--checkcould neverpass. It passed the previously committed file to uv as
--constraint, and uv thenrecords that file as a resolution source, injecting an annotation into the
# viablockof every constrained package:
The committed file is produced without that annotation, so the diff is non-empty forever.
Verified on this branch: generating all five files, then running the pre-fix script's
--checkagainst them immediately, reports all fiveOUT OF DATEand exits1.Separately, the lint job has no
astral-sh/setup-uvstep (unliketype-checkandunit-test), souvis not guaranteed to be onPATHthere at all — the reported"Resolution failed even without constraints" was a missing tool, not a broken dependency
graph. All five interpreters resolve cleanly; no dependency versions are changed here.
Solution:
scripts/update_constraints.sh— make it converge. Seed the candidate output filewith a copy of the committed pins instead of passing them via
--constraint. uv reads anexisting output file as version preferences, which gives the same pin stability
(
anthropicstays at its committed0.120.0rather than floating) without polluting theannotations. That also makes the two-attempt "retry without constraints" ladder dead code
— preferences can never make resolution fail — so it is deleted along with its misleading
error message. Net -12 lines.
.pre-commit-config.yaml— remove theupdate-constraintshook.pre-commitis afast, offline formatting gate; a five-way network dependency resolution that needs
uv,takes minutes, and mutates files does not belong in it — least of all under
--all-files, where it fires on every unrelated PR. Deliberately notstages: [manual]and not a
SKIP=in CI: both leave a hook nobody runs and hide the drift check ratherthan relocating it.
.github/workflows/constraints-check.yml(new) — relocate the drift check to a jobthat installs
uv, runs--check(report, don't rewrite), and only triggers onpyproject.toml,constraints-*.txt, orscripts/update_constraints.sh. Action SHAs arethe ones already pinned in
continuous-integration.yml;permissions: contents: readandtimeout-minutesmatch the existing style.constraints-3.10.txt…constraints-3.14.txtfiles that commit75c773edintended to publish. Generated by the script, never hand-typed —--checkparses
--exclude-newerback out of the header the script reconstructs, so a hand-writtenuvinvocation would silently break it.pyproject.toml[tool.codespell]skipmust exempt them: codespell flagsastroid(a real pylint dependency) as a misspelling of
asteroid, 6 occurrences, exit65.Without this the linter simply turns red again under a different hook. Deliberately not
ignore-words-list = astroid, which would weaken spell-checking of real prose repo-wide.The existing skip rationale ("generated or data files, not prose we own") already covers
them.
README.mdpointedcurlat the GitHub blob page, which serves HTML rather thanthe file; switched to
raw.githubusercontent.comso the documented flow actually works.Collision check (per contribution process).
gh pr list --repo AmaadMartin/adk-python --state open --limit 100was reviewed before any code was written. Two open PRs touch thisarea and neither lands this change:
fix/update-constraints-hook-manual-stage) adds a missing-uvfail-fast to thescript and sets the hook to
stages: [manual]. It does not commit the constraints files,does not fix the
--constraintnon-idempotence, and does not relocate the drift check, so--checkstill cannot pass. Itsuvfail-fast is complementary and is intentionally notduplicated here; this PR's new workflow installs
uvexplicitly.fix/lint-job-constraints-hook) applies the samestages: [manual]mitigation,bundled with ~55 files of unrelated mypy-typing churn.
This PR is not stacked on either: both adopt the
manual-stage approach that this changesupersedes (a hook nobody runs still leaves the drift unchecked), and #49's branch carries
unrelated churn that must not enter this diff. It branches from current
main. If #54 landsfirst, the conflict is confined to the single
.pre-commit-config.yamlhunk.Why one PR and not a stack: the hand-written change is ~200 lines across 6 files; the
remaining ~7,250 lines are one inert, machine-generated artifact commit. The commits are
atomic and separately reviewable, and splitting would create broken intermediate states —
removing the hook before the files exist, or committing files the fixed script has not yet
been able to validate.
Testing Plan
Please describe the tests that you ran to verify your changes. This is required for all PRs that are not small documentation or typo fixes.
Unit Tests:
[x] I have added or updated unit tests for my change.
[x] All unit tests pass locally.
New:
tests/unittests/test_constraints_files.py(17 cases), modelled on the existingtests/unittests/test_release_dependencies.pyrepo-hygiene guard and reusing its_find_pyproject()upward walk rather than inventing a second root resolver. No network, nosubprocess, no mocks; runs in 0.07s.
Every new test was proven able to fail by mutating the exact thing it pins:
constraints-3.10.txttest_constraints_file_exists_for_every_supported_version[3.10]constraints-3.10.txt is missing. README.md documents downloading it…--exclude-newer <date>fromconstraints-3.11.txtline 2test_constraints_header_is_script_generated[3.11]line 2 is not the command ./scripts/update_constraints.sh reconstructs, so --check cannot parse its --exclude-newer date back out# -c constraints-3.12.txt.stable.tmptest_constraints_files_have_no_annotation_leakage[3.12]references a temporary constraint file: ['# -c constraints-3.12.txt.stable.tmp']"3.14"fromPYTHON_VERSIONSin the scripttest_script_versions_match_python_classifiersscripts/update_constraints.sh and the pyproject.toml Python classifiers disagree on the supported interpretersupdate-constraintshook to.pre-commit-config.yamltest_precommit_does_not_run_constraints_scriptThe constraints updater is back in .pre-commit-config.yaml…Source-level mutation (the acceptance proof). With the pre-fix script from
mainandthese exact committed files,
--checkstill fails — which is the bug, and confirms the shellfix is load-bearing rather than incidental to committing the files:
Coverage. Measured with
--cov-branchover the new module. On Python 3.10 (the oldestinterpreter in the CI matrix): 96% line, 48 statements, 1 uncovered. The single uncovered
line is the
pytest.skip(...)body of the not-a-source-checkout guard, which cannot fire in asource checkout. It is not dead code and was verified manually — copying the module into a
tree that has
pyproject.tomlbut noscripts/producesSKIPPED [1] … Not a full source checkout: scripts/update_constraints.sh is absent.Thetomllib/tomliimport fallback is uncovered on 3.12 (92% there) but covered on 3.10, soboth branches execute across the matrix; it is the same compat shim
test_release_dependencies.pyalready uses and is required by the 3.10 job.Integration — the real tooling, no mocks. Every command below was run on this branch:
bash -n scripts/update_constraints.sh./scripts/update_constraints.sh(files absent)./scripts/update_constraints.sh --checkmain./scripts/update_constraints.sh --check(again)./scripts/update_constraints.sh(files present)pre-commit run --all-filescodespellArtifact sanity: each file starts with exactly two
#lines, line 2 ends with-o constraints-<ver>.txt, no^#\s+-c .*\.tmp$lines, no trailing whitespace, no CRLF, LFline endings with a single trailing newline (so
end-of-file-fixerandtrailing-whitespaceleave them alone). No
constraints-*.tmpfiles are left behind.CI on this PR, against the base-commit baseline. The base commit
6bab08fhas its ownred CI run, so here is the honest before/after rather than a bare "green":
6bab08fuv, real PyPI, path filter matchedThe Unit Tests failure is the same single test on both:
tests/unittests/cli/utils/test_cli_tools_click.py::test_telemetry_cli_commands, which failson all five interpreters at the base commit too. It is a Click >= 8.2 exit-code regression in
the
adk telemetryCLI — unrelated to this change, already the subject of separate open PRs,and deliberately not fixed here to avoid colliding with them.
The per-interpreter pass counts confirm nothing else moved and that the new tests really ran
in CI:
9202 → 9219,9204 → 9221(×3),9211 → 9228— +17 passed on every interpreter,exactly the 17 cases in
test_constraints_files.py, with1 failedunchanged.Manual End-to-End (E2E) Tests:
Please provide instructions on how to manually test your changes, including any necessary setup or configuration.
The user-facing promise in
README.md— the published artifact is installable. Ranagainst a real Python 3.10 environment and PyPI:
uv venv /tmp/adk-c --python 3.10 /tmp/adk-c/bin/pip install --dry-run google-adk -c constraints-3.10.txt # exit 0Resolves with no conflict. Also confirm the download line itself now works:
curl -o constraints-3.10.txt https://raw.githubusercontent.com/google/adk-python/main/constraints-3.10.txtreturns the file rather than an HTML page (the
blobURL it replaces does not).The lint gate is green from a clean checkout — the bug reproduction from
main:The new workflow's path filter.
Constraints Up To Dateshould appear on this PR(it touches
pyproject.toml,constraints-*.txt, andscripts/update_constraints.sh) andmust not appear on a PR that only touches
src/**/*.py.Checklist
[x] I have read the CONTRIBUTING.md document.
[x] I have performed a self-review of my own code.
[x] I have commented my code, particularly in hard-to-understand areas.
[x] I have added tests that prove my fix is effective or that my feature works.
[x] New and existing unit tests pass locally with my changes.