Skip to content

ci(release): mirror ci.yml's network hardening in the release gate, ship it as v4.17.1 - #335

Merged
cdeust merged 2 commits into
mainfrom
fix/release-workflow-network-hardening
Aug 2, 2026
Merged

ci(release): mirror ci.yml's network hardening in the release gate, ship it as v4.17.1#335
cdeust merged 2 commits into
mainfrom
fix/release-workflow-network-hardening

Conversation

@cdeust

@cdeust cdeust commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Why

v4.17.0 is tagged but published nothing. Run 30741657854 failed in the test job and blocked all five downstream publish jobs — no GitHub release, no PyPI upload, no .mcpb bundle exists for that tag.

The cause is a trigger asymmetry, not a flake:

# ci.yml:4-8
on:
  push:
    branches: [main]
  pull_request:

A tag push never reaches ci.yml. So every network-hardening pass CI absorbed since 2026-07-27 silently skipped release.yml, while both files kept running the same suite. The two jobs diverged with nothing to report it.

What that cost:

test_spell_alteration.py::test_recall_real_spell_by_name
  -> pg_recall.py:443
  -> reranker.py:109
  -> FlashRank: requests.get(..., stream=True)   # no timeout
  -> hang in sock.connect
  -> pytest-timeout kills the suite

HF_HUB_OFFLINE does not reach FlashRank's own fetch path, and reranker.py's except Exception cannot engage against a hang that never raises. The tree had just passed 20 green checks on #334.

What changed

Commit 1 — release.yml's test job mirrors ci.yml:103-115,142-195 one-for-one (cache + retried prefetch + offline run, per model):

  • cache ~/.cache/flashrank, matching reranker_model.py:104-113's reranker_cache_dir() (which honours $XDG_CACHE_HOME);
  • prefetch the reranker via ensure_reranker_loaded() asserting state == 'loaded' — a failed fetch fails the step instead of resurfacing later as first-stage-only recall scores (the 2026-07-10 FlashRank incident);
  • harden the HF prefetch to 5 retries with backoff and drop continue-on-error — a blip must not leave the cache empty and cascade into a misleading test failure;
  • run pytest under HF_HUB_OFFLINE / TRANSFORMERS_OFFLINE / CORTEX_RERANKER_OFFLINE, so no download can happen mid-suite.

ci.yml's three tree-sitter steps are deliberately NOT ported. requirements/release.txt omits tree-sitter and tree-sitter-language-pack (as it omits igraph, leidenalg, texttable), so the AST tests skip in this job and there is no grammar to fetch — porting them would have failed on ImportError. A comment records the corollary: this gate tests a narrower surface than CI, and the three steps must follow if release.txt ever gains that dependency.

Commit 2 — bump to v4.17.1. No source change; v4.17.1 carries v4.17.0's tree plus the workflow fix.

Why a new version rather than moving the tag

A tag executes the release.yml of its own tree. v4.17.0 points at 13278df4, which carries the unhardened file — a re-run would replay the same hang. Recreating the tag would rewrite a published ref and break the "tree bit-identical to ae633a87" property the v4.17.0 release decision rests on. House precedent: v3.15.2 abandoned → v3.15.3.

workflow_dispatch was not an option either: the publish jobs gate on event_name == 'push'.

Verification

  • ensure_reranker_loaded()state='loaded' in a local .venv;
  • generate_repo_badges.py --check (4 badges), check_doc_claims.py, check_marketplace_pins.py — all exit 0;
  • every touched JSON parses;
  • actionlint is not installed on this machine, so workflow validation was limited to a YAML parse plus review. CI on this PR is the real check.

Follow-up (not in this PR)

The durable fix for the divergence is a shared workflow_call between ci.yml and release.yml. Set aside today in favour of alignment; issue to follow.

🤖 Generated with Claude Code

cdeust and others added 2 commits August 2, 2026 11:56
Tag pushes run `release.yml`'s own `test` job, but `ci.yml` triggers on
`push: branches: [main]` + `pull_request` (ci.yml:4-8), so no tag has ever
reached it. Every hardening pass CI absorbed since 2026-07-27 therefore
skipped this file, and the two jobs silently diverged while running the same
suite.

Release run 30741657854 (v4.17.0) is what that divergence cost:
test_spell_alteration.py::test_recall_real_spell_by_name -> pg_recall.py:443
-> reranker.py:109 -> FlashRank's bare `requests.get(..., stream=True)`, which
carries no timeout, so a stalled connect hung in `sock.connect` until
pytest-timeout killed the suite. HF_HUB_OFFLINE does not reach FlashRank's own
fetch path, and reranker.py's `except Exception` cannot engage against a hang
that never raises. All five downstream publish jobs were blocked on a tag whose
tree had just passed 20 green checks on PR #334; the release shipped zero
artifacts.

Ported one-for-one from ci.yml:103-115,142-195 — cache + retried prefetch +
offline test run, per model:
- cache `~/.cache/flashrank`, matching reranker_model.py:104-113's
  `reranker_cache_dir()` (which honours $XDG_CACHE_HOME);
- prefetch the reranker via `ensure_reranker_loaded()` and assert
  `state == 'loaded'`, so a failed fetch fails the step instead of surfacing
  later as first-stage-only recall scores (the 2026-07-10 FlashRank incident);
- harden the HF prefetch to 5 retries with backoff and drop
  `continue-on-error`, so a blip cannot leave the cache empty and cascade into
  a misleading test failure;
- run pytest with HF_HUB_OFFLINE / TRANSFORMERS_OFFLINE /
  CORTEX_RERANKER_OFFLINE, so no model download can happen mid-suite.

ci.yml's three tree-sitter steps are deliberately NOT ported: requirements/
release.txt omits tree-sitter and tree-sitter-language-pack (as it omits
igraph, leidenalg and texttable), so the AST tests skip in this job and there
is no grammar to fetch — porting them would have failed on ImportError. A
comment records the corollary: this gate tests a narrower surface than CI, and
the three steps must follow if release.txt ever gains that dependency.

Verified locally in .venv: `ensure_reranker_loaded()` returns state='loaded'.
actionlint is not installed on this machine, so workflow validation was limited
to a YAML parse plus review.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… works

v4.17.0 was tagged but published nothing: its `test` job hung, blocking all
five downstream publish jobs, so no GitHub release, no PyPI upload and no
.mcpb bundle exist for it. The cause and its fix are the preceding commit.

A new patch version rather than a moved tag, because a tag executes the
`release.yml` of ITS OWN tree: `v4.17.0` points at 13278df, which carries the
unhardened workflow, so a re-run would replay the same hang; and recreating a
published tag would break the "tree bit-identical to ae633a8" property the
v4.17.0 release decision rests on. House precedent: v3.15.2 abandoned ->
v3.15.3. v4.17.1 therefore carries v4.17.0's tree plus the workflow fix — no
source change.

Version surfaces moved 4.17.0 -> 4.17.1 across the nine sites that carry the
release identity: pyproject.toml, server.json (document + pypi package),
manifest.json, .claude-plugin/plugin.json, .claude-plugin/marketplace.json
(metadata.version + the hypermnesia-mcp entry), uv.lock's root package,
package.json, and the generated assets/badge-version.svg. The deprecated
`cortex` marketplace entry stays pinned at 4.15.0 — a migration shim, not a
shipped version. README's badge alt text, docs/ROADMAP.md's "where the project
is today" line, and .bestpractices.json's three version justifications follow.

manifest.json matters beyond bookkeeping here: the mcpb-bundle job refuses to
pack when its version does not equal the tag (release.yml:419-421), so this is
the file the v4.17.1 tag will be checked against.

Gates: generate_repo_badges.py --check (4 badges), check_doc_claims.py, and
check_marketplace_pins.py all exit 0; every touched JSON parses.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cdeust
cdeust merged commit dd98611 into main Aug 2, 2026
20 checks passed
@cdeust
cdeust deleted the fix/release-workflow-network-hardening branch August 2, 2026 10:19
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.

1 participant