Skip to content

fix(launcher): strip extras from the pip constraints file - #339

Merged
cdeust merged 1 commit into
mainfrom
fix/constraints-cannot-have-extras
Aug 2, 2026
Merged

fix(launcher): strip extras from the pip constraints file#339
cdeust merged 1 commit into
mainfrom
fix/constraints-cannot-have-extras

Conversation

@cdeust

@cdeust cdeust commented Aug 2, 2026

Copy link
Copy Markdown
Owner

The bug

ensure_all_deps passes BASE_PACKAGES verbatim as the -c constraints file (launcher_deps.py:318), and one entry carries an extra — psycopg[binary]==3.3.4 (launcher_pins.py:89). pip has always documented constraints files as version-only and now rejects extras outright, so the entire ML resolve aborts:

ERROR: Constraints cannot have extras
[cortex-launcher] dependency install failed for sentence-transformers==5.6.1, flashrank==0.2.10

sentence-transformers and flashrank never install.

Why it went unnoticed

Only the ML install passes constraints. The base stack installs clean, so nothing fails visibly — the FlashRank re-ranker is simply absent, and recall silently degrades to first-stage-only scores. Same observable shape as the 2026-07-10 FlashRank incident.

Hit on a real plugin install (fresh hypermnesia-mcp@cortex-plugins 4.17.1, macOS).

The fix

pip_install normalizes each constraint through the new constraint_without_extras before writing the file.

This restores the parameter's own documented contract rather than changing it — its docstring already promised name==ver. A constraint pins the VERSION a shared transitive resolves to (numpy, via sentence-transformers), and pip applies it to the distribution however its extras were requested. Extras belong on the install target, which still carries [binary].

Version range — not pip-26-only

I first attributed this to pip 26. That was wrong: pip 25.2 rejects it too, found while validating against the project venv. The affected population is considerably wider than the pip-26 leading edge. Both measurements are now in the source comment and CHANGELOG.

Verification — external signals

pip itself judges the generated file:

verdict
before ERROR: Constraints cannot have extras
after 0 occurrences

Command: pip install --dry-run --no-index -c <BASE_PACKAGES> flashrank==0.2.10, run on pip 26.0.1/py3.14 and pip 25.2/py3.13.

  • Regression test fails without the fix, passes with it — it asserts the bytes written to the constraints file (what pip actually parses), not the helper: a correct helper wired in at the wrong place would still ship the bug.
  • A third test locks the real BASE_PACKAGES list so a future extra cannot reintroduce this.
  • tests_py/scripts/: 578 passed, 121 subtests.
  • ruff check + ruff format --check: clean.
  • launcher_deps_install.py stays within the repo's 300-line cap (300/300).

Note for release

Per CLAUDE.md, this delivers nothing to installs until the marketplace pins move — the fix needs a release + .claude-plugin/marketplace.json bump with scripts/check_marketplace_pins.py exiting 0.

🤖 Generated with Claude Code

`ensure_all_deps` passes BASE_PACKAGES verbatim as the `-c` constraints
file, and `psycopg[binary]==3.3.4` carries an extra. pip documents
constraints files as version-only and now rejects extras outright, so the
whole ML resolve aborted with "ERROR: Constraints cannot have extras" —
sentence-transformers and flashrank never installed.

The failure was silent: only the ML install passes constraints, so the base
stack installed clean and the FlashRank re-ranker was simply absent, leaving
recall on first-stage scores with no failing check — the same observable
shape as the 2026-07-10 FlashRank incident.

`pip_install` now normalizes each constraint through `constraint_without_extras`.
This restores the parameter's own documented contract (`name==ver`) rather
than changing it: a constraint pins the VERSION a shared transitive resolves
to, and pip applies it to the distribution however its extras were requested
— the install target still carries `[binary]`.

Measured 2026-08-02 on pip 26.0.1/py3.14, reproduced on pip 25.2/py3.13, so
the affected range is not pip-26-only; both accept the stripped file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cdeust
cdeust merged commit 3a1c3b6 into main Aug 2, 2026
19 checks passed
@cdeust
cdeust deleted the fix/constraints-cannot-have-extras branch August 2, 2026 15:32
cdeust added a commit that referenced this pull request Aug 2, 2026
Ships #339. Bumps every version site the release gates guard, so the fix
actually lands in plugin installs rather than stopping at the tag:
pyproject.toml, uv.lock (root package), .claude-plugin/plugin.json,
.claude-plugin/marketplace.json (metadata + the hypermnesia-mcp entry),
server.json (version + packages[0]), manifest.json, package.json, and the
regenerated assets/badge-version.svg.

Gates green: check_marketplace_pins.py ("All marketplace pins current"),
check_doc_claims.py, check_venv_lock_parity.py; tests_py/scripts 578 passed;
ruff clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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