Skip to content

Pin dependency versions exactly#3214

Closed
rbren wants to merge 1 commit into
mainfrom
pin-exact-dependencies
Closed

Pin dependency versions exactly#3214
rbren wants to merge 1 commit into
mainfrom
pin-exact-dependencies

Conversation

@rbren

@rbren rbren commented May 12, 2026

Copy link
Copy Markdown
Member

Summary

  • Pin Python package dependencies across SDK workspace packages to uv-lock-resolved exact versions
  • Pin build-system requirements and workspace-wide uv dependency constraints to exact versions
  • Refresh uv.lock metadata after pinning

Validation

  • uv lock
  • uv lock --check
  • Custom exact-spec scan for Python direct dependencies

This PR was created by an AI agent (OpenHands) on behalf of the user.


Agent Server images for this PR

GHCR package: https://github.com/OpenHands/agent-sdk/pkgs/container/agent-server

Variants & Base Images

Variant Architectures Base Image Docs / Tags
java amd64, arm64 eclipse-temurin:17-jdk Link
python amd64, arm64 nikolaik/python-nodejs:python3.13-nodejs22-slim Link
golang amd64, arm64 golang:1.21-bookworm Link

Pull (multi-arch manifest)

# Each variant is a multi-arch manifest supporting both amd64 and arm64
docker pull ghcr.io/openhands/agent-server:ddd542c-python

Run

docker run -it --rm \
  -p 8000:8000 \
  --name agent-server-ddd542c-python \
  ghcr.io/openhands/agent-server:ddd542c-python

All tags pushed for this build

ghcr.io/openhands/agent-server:ddd542c-golang-amd64
ghcr.io/openhands/agent-server:ddd542c97edebbb4643aed566e3fb4eddbecbe76-golang-amd64
ghcr.io/openhands/agent-server:pin-exact-dependencies-golang-amd64
ghcr.io/openhands/agent-server:ddd542c-golang_tag_1.21-bookworm-amd64
ghcr.io/openhands/agent-server:ddd542c-golang-arm64
ghcr.io/openhands/agent-server:ddd542c97edebbb4643aed566e3fb4eddbecbe76-golang-arm64
ghcr.io/openhands/agent-server:pin-exact-dependencies-golang-arm64
ghcr.io/openhands/agent-server:ddd542c-golang_tag_1.21-bookworm-arm64
ghcr.io/openhands/agent-server:ddd542c-java-amd64
ghcr.io/openhands/agent-server:ddd542c97edebbb4643aed566e3fb4eddbecbe76-java-amd64
ghcr.io/openhands/agent-server:pin-exact-dependencies-java-amd64
ghcr.io/openhands/agent-server:ddd542c-eclipse-temurin_tag_17-jdk-amd64
ghcr.io/openhands/agent-server:ddd542c-java-arm64
ghcr.io/openhands/agent-server:ddd542c97edebbb4643aed566e3fb4eddbecbe76-java-arm64
ghcr.io/openhands/agent-server:pin-exact-dependencies-java-arm64
ghcr.io/openhands/agent-server:ddd542c-eclipse-temurin_tag_17-jdk-arm64
ghcr.io/openhands/agent-server:ddd542c-python-amd64
ghcr.io/openhands/agent-server:ddd542c97edebbb4643aed566e3fb4eddbecbe76-python-amd64
ghcr.io/openhands/agent-server:pin-exact-dependencies-python-amd64
ghcr.io/openhands/agent-server:ddd542c-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-amd64
ghcr.io/openhands/agent-server:ddd542c-python-arm64
ghcr.io/openhands/agent-server:ddd542c97edebbb4643aed566e3fb4eddbecbe76-python-arm64
ghcr.io/openhands/agent-server:pin-exact-dependencies-python-arm64
ghcr.io/openhands/agent-server:ddd542c-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-arm64
ghcr.io/openhands/agent-server:ddd542c-golang
ghcr.io/openhands/agent-server:ddd542c97edebbb4643aed566e3fb4eddbecbe76-golang
ghcr.io/openhands/agent-server:pin-exact-dependencies-golang
ghcr.io/openhands/agent-server:ddd542c-golang_tag_1.21-bookworm
ghcr.io/openhands/agent-server:ddd542c-java
ghcr.io/openhands/agent-server:ddd542c97edebbb4643aed566e3fb4eddbecbe76-java
ghcr.io/openhands/agent-server:pin-exact-dependencies-java
ghcr.io/openhands/agent-server:ddd542c-eclipse-temurin_tag_17-jdk
ghcr.io/openhands/agent-server:ddd542c-python
ghcr.io/openhands/agent-server:ddd542c97edebbb4643aed566e3fb4eddbecbe76-python
ghcr.io/openhands/agent-server:pin-exact-dependencies-python
ghcr.io/openhands/agent-server:ddd542c-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim

About Multi-Architecture Support

  • Each variant tag (e.g., ddd542c-python) is a multi-arch manifest supporting both amd64 and arm64
  • Docker automatically pulls the correct architecture for your platform
  • Individual architecture tags (e.g., ddd542c-python-amd64) are also available if needed

Co-authored-by: openhands <openhands@all-hands.dev>
@github-actions

Copy link
Copy Markdown
Contributor

Python API breakage checks — ✅ PASSED

Result:PASSED

Action log

@github-actions

Copy link
Copy Markdown
Contributor

REST API breakage checks (OpenAPI) — ✅ PASSED

Result:PASSED

Action log

@all-hands-bot all-hands-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔴 Needs improvement - This pinning strategy violates Python packaging best practices and will create significant issues for downstream users.

Comment thread openhands-sdk/pyproject.toml
Comment thread pyproject.toml
Comment thread openhands-agent-server/pyproject.toml
Comment thread openhands-sdk/pyproject.toml
Comment thread uv.lock

@all-hands-bot all-hands-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

✅ QA Report: PASS

All Python dependencies successfully pinned to exact versions across the SDK workspace. Environment builds cleanly, lockfile is stable, and installed packages match specifications.

Does this PR achieve its stated goal?

Yes. The PR successfully pins all Python package dependencies to exact == versions across all four workspace packages (openhands-sdk, openhands-tools, openhands-workspace, openhands-agent-server), including dependencies, constraint-dependencies, dev dependency groups, and build-system requirements. The lockfile is in sync and the environment syncs successfully with all pinned versions.

Phase Result
Environment Setup ✅ uv 0.11.13 available, no issues
CI Status ✅ 13 checks passed (pre-commit, sdk-tests, agent-server-tests, workspace-tests, etc.), several in progress
Functional Verification ✅ All verifications passed
Functional Verification

Test 1: Verify All Dependencies Use Exact Version Pins

Step 1 — Check baseline (main branch specs):
Ran git show origin/main:openhands-sdk/pyproject.toml | grep -A15 "^dependencies":

dependencies = [
    "agent-client-protocol>=0.8.1",
    "deprecation>=2.1.0",
    "fakeredis[lua]>=2.32.1",
    "fastmcp>=3.0.0",
    "filelock>=3.20.1",
    "httpx[socks]>=0.27.0",
    "joserfc>=1.0.0",
    "litellm>=1.83.7",
    "pillow>=12.1.1",
    "pydantic>=2.12.5",
    ...

This shows the original state used >= range specifiers allowing any version above the minimum.

Step 2 — Apply the PR's changes:
Checked out PR branch pin-exact-dependencies (commit ddd542c).

Step 3 — Verify exact pinning:
Ran custom scanner to check all pyproject.toml files for non-exact specifiers:

python3 /tmp/check_exact_pins.py
✅ All dependencies use exact version pins (==)

Manually verified the PR branch specs:

dependencies = [
    "agent-client-protocol==0.8.1",
    "deprecation==2.1.0",
    "fakeredis[lua]==2.34.1",
    "fastmcp==3.2.0",
    "filelock==3.20.3",
    "httpx[socks]==0.28.1",
    "joserfc==1.6.4",
    "litellm==1.83.14",
    "pillow==12.2.0",
    "pydantic==2.12.5",
    ...

This confirms all >= specifiers were converted to == exact pins.

Test 2: Verify Lockfile Synchronization

Verification:
Ran uv lock --check:

Using CPython 3.13.13
Resolved 403 packages in 1ms

Exit code 0 confirms the lockfile is perfectly in sync with the pinned pyproject.toml specs.

Test 3: Verify Environment Builds with Pinned Dependencies

Step 1 — Sync production dependencies:
Ran uv sync --no-dev:

Using CPython 3.13.13
Creating virtual environment at: .venv
Resolved 403 packages in 1ms
   Building openhands-tools
   Building openhands-workspace
   Building openhands-sdk
   Building openhands-agent-server
...
[successful completion]

Step 2 — Verify installed versions match pinned specs:
Ran version checks for key packages:

pydantic: 2.12.5      # matches openhands-sdk ==2.12.5 ✓
fastapi: 0.129.0      # matches openhands-agent-server ==0.129.0 ✓
websockets: 15.0.1    # matches both packages ==15.0.1 ✓
sqlalchemy: 2.0.44    # matches openhands-agent-server ==2.0.44 ✓

Step 3 — Sync dev dependencies:
Ran uv sync --dev:

+ pytest==9.0.3
+ pytest-cov==7.0.0
+ ruff==0.13.1
+ pyright==1.1.407
+ streamlit==1.54.0
...

All dev dependencies also installed at exact pinned versions.

Test 4: Verify SDK Functionality

Verification:
Ran SDK import test:

from openhands.sdk import Agent, Conversation, LLM, Tool
from openhands.tools.terminal import TerminalTool
from openhands.tools.file_editor import FileEditorTool
print('✅ All core SDK imports successful')

Output:

✅ All core SDK imports successful
Agent: <class 'openhands.sdk.agent.agent.Agent'>
Conversation: <class 'openhands.sdk.conversation.conversation.Conversation'>
LLM: <class 'openhands.sdk.llm.llm.LLM'>

Core SDK functionality works correctly with all pinned dependencies.

Test 5: Verify Lockfile Stability

Verification:
Ran uv lock again to verify deterministic lockfile:

cp uv.lock uv.lock.backup
uv lock
diff uv.lock.backup uv.lock
✅ Lockfile unchanged after re-locking

Lockfile is stable and reproducible.

Test 6: Verify All Dependency Categories Were Pinned

Verified pinning in:

  1. Direct dependencies (all 4 packages):

    • openhands-sdk/pyproject.toml: 15 deps pinned
    • openhands-tools/pyproject.toml: 9 deps pinned
    • openhands-agent-server/pyproject.toml: 9 deps pinned
    • openhands-workspace/pyproject.toml: 3 deps pinned
  2. Constraint dependencies (security fixes):

    # Before:
    "starlette>=0.49.1", "aiohttp>=3.13.3", "urllib3>=2.6.3", ...
    
    # After:
    "starlette==0.52.1", "aiohttp==3.13.4", "urllib3==2.6.3", ...
    
  3. Dev dependency groups:

    # Before:
    "pytest>=9.0.3", "ruff>=0.12.10", "pyright[nodejs]>=1.1.405", ...
    
    # After:
    "pytest==9.0.3", "ruff==0.13.1", "pyright[nodejs]==1.1.407", ...
    
  4. Build-system requirements:

    # Before:
    requires = ["setuptools>=61.0", "wheel"]
    
    # After:
    requires = ["setuptools==80.9.0", "wheel==0.46.3"]
    

Issues Found

None.

@github-actions

Copy link
Copy Markdown
Contributor

Coverage

Coverage Report •
FileStmtsMissCoverMissing
TOTAL27395610277% 
report-only-changed-files is enabled. No files were changed during this commit :)

rbren commented May 12, 2026

Copy link
Copy Markdown
Member Author

For reviewer context: this exact-pinning change is intended as a supply-chain mitigation, not only a reproducibility cleanup. Recent npm/PyPI supply-chain attacks have shown that flexible dependency ranges can silently admit newly compromised releases when a lockfile is regenerated, omitted, or bypassed by a different install/publish path. Exact manifest pins complement lockfiles by preventing resolver drift; security and compatibility updates should happen through deliberate dependency update PRs where the diff is reviewable.

This comment was created by an AI agent (OpenHands) on behalf of the user.

rbren commented May 12, 2026

Copy link
Copy Markdown
Member Author

I rechecked this PR against origin/main:uv.lock for effective downgrades. No pinned SDK dependency version is lower than the version already present in the existing lockfile, so there were no SDK changes to push for this specific issue.

Validation rerun:

  • uv lock --check
  • exact-spec scan for direct Python dependencies
  • comparison scan confirmed 0 pinned versions lower than origin/main:uv.lock

This comment was created by an AI agent (OpenHands) on behalf of the user.

@rbren rbren requested a review from all-hands-bot May 12, 2026 00:37

@all-hands-bot all-hands-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

While the supply-chain security intent is understood, this pinning strategy will cause real breakage for downstream users. See inline comments for specific scenarios and better alternatives.

Comment on lines +8 to +22
"agent-client-protocol==0.8.1",
"deprecation==2.1.0",
"fakeredis[lua]==2.34.1", # Explicit dependency for docket/fastmcp background tasks
"fastmcp==3.2.0",
"filelock==3.20.3",
"httpx[socks]==0.28.1",
"joserfc==1.6.4",
"litellm==1.83.14",
"pillow==12.2.0",
"pydantic==2.12.5",
"python-frontmatter==1.1.0",
"python-json-logger==4.0.0",
"tenacity==9.1.2",
"websockets==15.0.1",
"lmnr==0.7.47",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔴 Critical - Downstream dependency conflicts: Pinning exact versions in library packages creates dependency resolution failures for users.

Concrete scenario:

# User's project
requires = [
    "openhands-sdk==1.22.0",  # pins pydantic==2.12.5
    "fastapi==0.110.0"        # requires pydantic>=2.13.0
]
# Resolution FAILS - cannot install both

Why lock file + exclude-newer is sufficient for security:

  • First-party (this repo): uv.lock + exclude-newer = "7 days" already blocks compromised releases
  • Third-party (downstream users): They resolve dependencies in their own project context anyway

Better approach:

  1. Keep uv.lock exact (✅ already done)
  2. Keep pyproject.toml dependencies permissive (e.g., pydantic>=2.12.5,<3)
  3. Publish a constraints.txt for users who want reproducible installs
  4. Document: uv pip install openhands-sdk --constraint constraints.txt

This achieves security for those who opt-in without forcing dependency hell on everyone.

Comment thread pyproject.toml
Comment on lines +9 to +16
"starlette==0.52.1", # CVE-2025-62727
"aiohttp==3.13.4", # CVE-2025-69223 + 7 others
"urllib3==2.6.3", # CVE-2026-21441, CVE-2025-66471, CVE-2025-66418
"protobuf==6.33.5", # CVE-2026-0994
"pillow==12.2.0", # CVE-2026-25990
"orjson==3.11.7", # CVE-2025-67221
"rich==14.3.3", # Version 14.3.2 essentially has a denial-of-service vulnerability which is outlined in https://github.com/Textualize/rich/issues/3958
"lupa==2.8", # CVE-2026-34444

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🟠 Important - Security patches blocked: Converting security constraints from >= to == prevents automatic security updates.

Scenario:

  • Today: starlette==0.52.1 (pinned here)
  • Tomorrow: CVE-2026-XXXXX discovered in 0.52.1
  • Fix released: starlette==0.52.2
  • Result: Users stuck on vulnerable 0.52.1

For security constraints specifically, >= is correct because:

  • These are minimum versions that fix known CVEs
  • Pinning to == defeats the purpose of having security constraints
  • The exclude-newer guardrail prevents pulling in compromised NEW releases
  • But users still need the ability to get security PATCHES

Recommendation: Keep security constraints as >= minimum versions, rely on uv.lock for reproducibility.


[build-system]
requires = ["setuptools>=61.0", "wheel"]
requires = ["setuptools==80.9.0", "wheel==0.46.3"]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🟡 Suggestion - Build system pins are fragile: Pinning setuptools and wheel to exact versions can break builds in different environments where build tools expect newer versions.

This is lower priority than the library dependency issue, but worth noting that build-system pins are particularly problematic because different build frontends (pip, uv, build, etc.) may have different requirements.

@all-hands-bot all-hands-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

✅ QA Report: PASS

Dependency pinning implemented successfully across all workspace packages.

Does this PR achieve its stated goal?

Yes. This PR successfully pins all Python package dependencies to exact versions using == specifiers across the entire SDK workspace. Verification confirms:

  • All 402 packages in uv.lock match the pinned versions in pyproject.toml files
  • Build-system requirements, constraint-dependencies, and dev dependencies all use exact pins
  • The lock file was refreshed with the correct exclude-newer timestamp (7 days policy)
  • The SDK continues to function correctly with the pinned dependencies

This achieves the stated goal of ensuring reproducibility, security, and stability through exact version constraints.

Phase Result
Environment Setup uv sync and uv lock --check passed
CI Status ℹ️ Not checked - QA focused on functional verification
Functional Verification ✅ All verifications passed
Functional Verification

Test 1: Verify exact version pins in pyproject.toml files

Verification approach:
Scanned all workspace pyproject.toml files to verify dependencies use == (not >=, ~=, or ranges).

Command:

# Custom Python script to scan all pyproject.toml files
uv run python /tmp/check_exact_pins.py

Result:

✅ All dependencies use exact version pins (==)

Interpretation: All direct dependencies across openhands-sdk, openhands-tools, openhands-agent-server, openhands-workspace, and root workspace use exact version specifiers. No range specifiers (>=, <, ~=) found in project-controlled files.


Test 2: Verify lock file consistency

Verification approach:
Compared pinned versions in pyproject.toml files against resolved versions in uv.lock to ensure they match.

Command:

# Check that uv.lock is consistent with pyproject.toml
uv lock --check

Result:

Using CPython 3.13.13
Resolved 403 packages in 1ms

Command:

# Verify pinned versions match lock file
uv run python /tmp/verify_lock_consistency.py

Result:

Parsed 402 packages from uv.lock

✅ All pinned versions in pyproject.toml files match uv.lock

Interpretation: The lock file is fully consistent with the pinned dependency specifications. All 402 resolved packages match the exact versions declared in the workspace pyproject.toml files.


Test 3: Verify build-system requirements are pinned

Command:

grep -A 2 "[build-system]" openhands-*/pyproject.toml | grep "requires ="

Result:

openhands-agent-server/pyproject.toml-requires = ["setuptools==80.9.0", "wheel==0.46.3"]
openhands-sdk/pyproject.toml-requires = ["setuptools==80.9.0", "wheel==0.46.3"]
openhands-tools/pyproject.toml-requires = ["setuptools==80.9.0", "wheel==0.46.3"]
openhands-workspace/pyproject.toml-requires = ["setuptools==80.9.0", "wheel==0.46.3"]

Interpretation: All build-system requirements across all workspace packages are pinned to exact versions.


Test 4: Verify constraint-dependencies are pinned

Command:

grep -A 10 "constraint-dependencies" pyproject.toml

Result:

constraint-dependencies = [
    "starlette==0.52.1",  # CVE-2025-62727
    "aiohttp==3.13.4",  # CVE-2025-69223 + 7 others
    "urllib3==2.6.3",  # CVE-2026-21441, CVE-2025-66471, CVE-2025-66418
    "protobuf==6.33.5",  # CVE-2026-0994
    "pillow==12.2.0",  # CVE-2026-25990
    "orjson==3.11.7",  # CVE-2025-67221
    "rich==14.3.3",
    "lupa==2.8",  # CVE-2026-34444
]

Interpretation: All security-related constraint-dependencies are pinned to exact versions, ensuring consistent vulnerability mitigation across the workspace.


Test 5: Verify uv.lock was refreshed

Command:

grep -A 2 "[options]" uv.lock

Result:

[options]
exclude-newer = "2026-05-05T00:13:55.25457273Z"
exclude-newer-span = "P7D"

Interpretation: The lock file was refreshed with a specific timestamp (7 days before current date: 2026-05-12), matching the workspace's "exclude-newer = 7 days" policy. This confirms the lock file is up-to-date.


Test 6: Verify SDK functionality with pinned dependencies

Verification approach:
Test that the SDK can initialize and import successfully with the exact pinned versions.

Command:

OPENHANDS_SUPPRESS_BANNER=1 uv run python -c "from openhands.sdk import Agent, LLM, Conversation, Tool; from openhands.tools.terminal import TerminalTool; print('✅ SDK works')"

Result:

✅ SDK works

Interpretation: Core SDK modules and tools import and initialize successfully with the pinned dependency versions, confirming no breaking dependency conflicts introduced by exact pinning.

Issues Found

None.


QA methodology: Verified dependency pinning through automated scanning of pyproject.toml files, lock file consistency checks, and functional SDK initialization tests. All verifications passed.

@raymyers raymyers left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I tend to agree with the bot feedback, I don't think this is appropriate in a library package.

Pinning exact versions in library packages creates dependency resolution failures for users.

@rbren

rbren commented May 12, 2026

Copy link
Copy Markdown
Member Author

Good point Ray. Closing

@rbren rbren closed this May 12, 2026
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.

5 participants