Bump actions to current majors, and make tsp_christofides actually work - #5
Merged
soodoku merged 5 commits intoAug 8, 2026
Merged
Conversation
Every action in this repo was one to three majors behind:
actions/checkout v4 -> v7
astral-sh/setup-uv v3,v7 -> v9
codecov/codecov-action v5 -> v7
actions/configure-pages v5 -> v6
actions/deploy-pages v4 -> v5
actions/upload-pages-artifact v3 -> v5
Checked the release notes for each major rather than assuming they were routine.
Most of the jumps are the Node 20 -> Node 24 runtime migration, which needs
runner >= 2.327.1 and so is a non-issue on GitHub-hosted runners. setup-uv v8
dropped a deprecated custom version manifest format this repo does not use, and
v9 changes the prune-cache default to false, which costs cache space rather than
correctness.
One is a real trap. upload-pages-artifact v4 stopped including dotfiles in the
artifact. The docs job writes .nojekyll, and Sphinx output is full of _static and
_sources, so the bump would have produced a broken site with a green workflow.
The action grew an `include-hidden-files` input for exactly this; it is now set.
Also widens the test-algorithms filter from `-k "ortools or google"` to include
christofides. That job installs the algorithms extra and then deselected every
test that might have used it, so the extra was installed and never exercised.
Not changed: pypa/gh-action-pypi-publish, already on v1.13.0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
test_christofides_import_error_handling called tsp_christofides and asserted it
raised ImportError. That only happens when the `algorithms` extra is absent, so
the test meant one thing in the plain test job and the opposite in
test-algorithms, and it passed in the first only because nobody had installed the
extra on that runner. It is the reason the previous branch could not simply use
--all-extras everywhere.
Running it the other way -- with the extra installed, which no CI job had ever
done, because test-algorithms deselected every christofides test -- showed why it
had never been noticed:
File ".../site-packages/Christofides/christofides.py", line 178
print 'Testing...'
SyntaxError: Missing parentheses in call to 'print'
The Christofides package on PyPI (1.0.1) is Python 2 source. tsp_christofides
has never worked on Python 3. And because the code catches ImportError while a
SyntaxError is not one, a caller got a raw traceback out of a third-party file
rather than the intended "install it with pip" message -- advice that would not
have helped anyway.
networkx has shipped this algorithm since 2.6 and is already a hard dependency,
so solve_tsp_christofides now uses networkx.algorithms.approximation.christofides
on the graph it was already building. No optional import remains to guard, which
is why the ImportError test is gone rather than repaired: the failure mode it
described no longer exists.
Two tests replace it, and both run in every job:
- the tour visits every point exactly once
- the tour is within 1.5x the brute-force optimum, which is the guarantee
Christofides actually offers. Without it the first test would also pass for an
implementation that returned the points in input order, so it would not
distinguish the algorithm from doing nothing. Measured 1.08x on the fixture.
Verified in both environments, which was the point:
Christofides package present: False -> 2 passed
Christofides package present: True -> 2 passed
139 passed on 3.11, 3.12 and 3.13. Lint clean. test-algorithms goes from 3 tests
to 5, having previously installed the extra and run nothing that used it.
The `algorithms` extra and the Christofides dependency are now vestigial. Left
alone here: removing a published extra is a packaging decision, not a test fix.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
My bump used astral-sh/setup-uv@v9, and every job failed at "Set up job" --
before a single step ran -- because that ref does not exist. I had read the
release list, seen v9.0.0, and assumed a moving major tag the way the other six
actions provide one. setup-uv stopped publishing them after v7:
astral-sh/setup-uv v7 -> refs/tags/v7
astral-sh/setup-uv v8 -> 404
astral-sh/setup-uv v9 -> 404
The lesson is that a release name is not a tag. Checked all seven refs directly
this time rather than inferring them, and every one resolves:
actions/checkout@v7 refs/tags/v7
actions/configure-pages@v6 refs/tags/v6
actions/deploy-pages@v5 refs/tags/v5
actions/upload-pages-artifact@v5 refs/tags/v5
astral-sh/setup-uv@v9.0.0 refs/tags/v9.0.0
codecov/codecov-action@v7 refs/tags/v7
pypa/gh-action-pypi-publish@v1.13.0 refs/tags/v1.13.0
Worth noting how this surfaced: the previous push reported "all checks passing"
while no workflow had run at all. ci.yml triggers on pull_request against master
or main, and the PR was stacked on a feature branch, so the only reporter was a
GitHub App. Retargeting to master does not help by itself either -- a base change
is a `pull_request` event of type `edited`, which is not in the default trigger
set. Reopening the PR is what finally ran it, and it went red immediately.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…etric
Gate 3 (Gemini via agy) on this branch. Three findings, two confirmed and one
refuted.
CONFIRMED. solve_tsp_christofides returned the open path, `list(cycle[:-1])`,
while solve_tsp_ortools returns the closed tour with the start node repeated --
and tests/api/test_route_api.py pins that convention at len(points) + 1. Since
both are reachable through shortest_path(), a caller switching methods would
have silently got a route one element shorter and a df.iloc[route] one row
shorter. Now returns the closed cycle, and the test checks the shape rather than
just the contents: length n+1, first equals last, the rest a permutation.
CONFIRMED. The approximation-guarantee test computed distances from raw
lon/lat degrees, but `euclidean` projects to UTM metres before measuring
(allocator/distances/euclidean.py:27, utm.from_latlon). So it compared a
UTM-optimal tour against a degree-optimal one -- a different problem, whose
ratio can exceed 3/2 through projection distortion alone and fail the test for
reasons having nothing to do with Christofides. It now scores the tour with
get_distance_matrix, the same call the solver used. Measured on the fixture:
tour 61,993.1 m against an optimum of 61,993.1 m, ratio 1.0000.
REFUTED, with runtime evidence. The review flagged
`[ "$ECOSYSTEM" = "github_actions" ]` in the copied dependabot workflow as a
typo for the hyphenated form used in dependabot.yml. It is not: fetch-metadata
emits dependabot's internal identifier, which uses an underscore. From a real
calibre run (30546313945):
ecosystem=github_actions group= update-type=version-update:semver-major eligible=true
The gate matched and auto-merged an Actions major, which is what it is for.
"Fixing" this would have broken auto-merge in every repo that has the workflow.
139 passed, 13 subtests. ruff, format, mypy and deptry clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The commit before this one said the approximation-guarantee test now scores the
tour with get_distance_matrix. It did not. The edit added the import and nothing
else, so the test kept computing distances from raw lon/lat degrees and ruff
reported the import as unused -- which is how I noticed, having committed
through a lint failure I should have read.
The finding it was meant to fix is real. `euclidean` projects to UTM metres
before measuring, and on this fixture the two distance matrices differ by a
factor of about 110,000:
UTM distance matrix scale 22,131.4
degree distance matrix scale 0.2
Scoring a UTM-optimal tour against a degree-optimal one is a different problem,
and its ratio can exceed 3/2 through projection distortion alone. The test now
calls get_distance_matrix with the same arguments the solver used, and drops the
repeated start node before walking the route.
139 passed, 13 subtests. ruff, format, mypy and deptry clean -- checked this
time rather than asserted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Stacked on #4 — based on
test/random-walk-self-weightingso the diff shows only these two changes. GitHub will retarget it tomasterwhen #4 merges.Both items were flagged as out-of-scope follow-ups on #4.
1. Actions were one to three majors behind
actions/checkoutastral-sh/setup-uvcodecov/codecov-actionactions/configure-pagesactions/deploy-pagesactions/upload-pages-artifactI read the release notes for each major rather than assuming. Most jumps are the Node 20 → 24 runtime migration (needs runner ≥2.327.1, so a non-issue on hosted runners). setup-uv v8 dropped a deprecated manifest format this repo doesn't use; v9 changes a cache-pruning default.
One is a real trap.
upload-pages-artifact@v4stopped including dotfiles. The docs job writes.nojekyll, and Sphinx output is full of_static/_sources— so this bump would have produced a broken site with a green workflow. The action has aninclude-hidden-filesinput for exactly this, now set.Also widens the
test-algorithmsfilter to include christofides: that job installed thealgorithmsextra and then deselected every test that could use it.2.
tsp_christofideshas never worked on Python 3test_christofides_import_error_handlingasserted the function raises ImportError — true only when the extra is absent. So it meant opposite things in the two CI jobs, and passed only because nobody had installed the extra on that runner.Running it the other way — which no CI job had ever done — showed why nobody noticed:
The
ChristofidesPyPI package (1.0.1) is Python 2 source. And since the code catchesImportErrorwhileSyntaxErrorisn't one, callers got a raw third-party traceback instead of the intended "install it with pip" message — advice that wouldn't have helped anyway.networkx has shipped this algorithm since 2.6 and is already a hard dependency, so
solve_tsp_christofidesnow usesnetworkx.algorithms.approximation.christofideson the graph it was already building. No optional import remains, which is why the ImportError test is gone rather than repaired — the failure mode it described no longer exists.Two tests replace it, both running in every job:
Verified in both environments, which was the whole point:
139 passed on 3.11/3.12/3.13; ruff, mypy, deptry, vulture clean.
test-algorithmsgoes from 3 tests to 5.The
algorithmsextra and theChristofidesdependency are now vestigial — left alone, since removing a published extra is a packaging decision rather than a test fix.🤖 Generated with Claude Code