Pin dependency versions exactly#3214
Conversation
Co-authored-by: openhands <openhands@all-hands.dev>
Python API breakage checks — ✅ PASSEDResult: ✅ PASSED |
REST API breakage checks (OpenAPI) — ✅ PASSEDResult: ✅ PASSED |
all-hands-bot
left a comment
There was a problem hiding this comment.
🔴 Needs improvement - This pinning strategy violates Python packaging best practices and will create significant issues for downstream users.
all-hands-bot
left a comment
There was a problem hiding this comment.
✅ 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:
-
Direct dependencies (all 4 packages):
openhands-sdk/pyproject.toml: 15 deps pinnedopenhands-tools/pyproject.toml: 9 deps pinnedopenhands-agent-server/pyproject.toml: 9 deps pinnedopenhands-workspace/pyproject.toml: 3 deps pinned
-
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", ... -
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", ... -
Build-system requirements:
# Before: requires = ["setuptools>=61.0", "wheel"] # After: requires = ["setuptools==80.9.0", "wheel==0.46.3"]
Issues Found
None.
|
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. |
|
I rechecked this PR against Validation rerun:
This comment was created by an AI agent (OpenHands) on behalf of the user. |
all-hands-bot
left a comment
There was a problem hiding this comment.
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.
| "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", |
There was a problem hiding this comment.
🔴 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 bothWhy 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:
- Keep
uv.lockexact (✅ already done) - Keep
pyproject.tomldependencies permissive (e.g.,pydantic>=2.12.5,<3) - Publish a
constraints.txtfor users who want reproducible installs - Document:
uv pip install openhands-sdk --constraint constraints.txt
This achieves security for those who opt-in without forcing dependency hell on everyone.
| "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 |
There was a problem hiding this comment.
🟠 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-newerguardrail 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"] |
There was a problem hiding this comment.
🟡 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
left a comment
There was a problem hiding this comment.
✅ 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.pyResult:
✅ 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 --checkResult:
Using CPython 3.13.13
Resolved 403 packages in 1ms
Command:
# Verify pinned versions match lock file
uv run python /tmp/verify_lock_consistency.pyResult:
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.tomlResult:
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.lockResult:
[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
left a comment
There was a problem hiding this comment.
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.
|
Good point Ray. Closing |
Summary
uv.lockmetadata after pinningValidation
uv lockuv lock --checkThis 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
eclipse-temurin:17-jdknikolaik/python-nodejs:python3.13-nodejs22-slimgolang:1.21-bookwormPull (multi-arch manifest)
# Each variant is a multi-arch manifest supporting both amd64 and arm64 docker pull ghcr.io/openhands/agent-server:ddd542c-pythonRun
All tags pushed for this build
About Multi-Architecture Support
ddd542c-python) is a multi-arch manifest supporting both amd64 and arm64ddd542c-python-amd64) are also available if needed