Skip to content

fix(terminal): avoid importing Unix-only modules on Windows#2250

Closed
neubig wants to merge 2 commits intomainfrom
fix/windows-fcntl-import
Closed

fix(terminal): avoid importing Unix-only modules on Windows#2250
neubig wants to merge 2 commits intomainfrom
fix/windows-fcntl-import

Conversation

@neubig
Copy link
Copy Markdown
Contributor

@neubig neubig commented Mar 1, 2026

Summary

This PR fixes a ModuleNotFoundError: No module named 'fcntl' that occurs when importing openhands-tools on Windows.

Problem

The subprocess_terminal.py and tmux_terminal.py files use Unix-only modules (fcntl, pty, select) that don't exist on Windows. When the terminal/__init__.py file was imported, it unconditionally imported these modules, causing the import to fail on Windows before any code could execute.

Error trace from issue:

File "...\openhands_cli\utils.py", line 22, in <module>
    from openhands.tools.terminal import TerminalTool
  File "...\openhands\tools\terminal\__init__.py", line 7, in <module>
    from openhands.tools.terminal.terminal.subprocess_terminal import (
  File "...\subprocess_terminal.py", line 3, in <module>
    import fcntl
ModuleNotFoundError: No module named 'fcntl'

Solution

Make the imports of SubprocessTerminal and TmuxTerminal conditional on the platform:

  • On Unix-like systems (sys.platform != "win32"): Import all terminal backends as before
  • On Windows: Only import the base classes and factory function

The factory.py already handles Windows by raising NotImplementedError, but the import error occurred before the factory was even called.

Testing

This fix allows the module to be imported on Windows without errors. The actual terminal functionality will still raise NotImplementedError on Windows (as designed), but the import will succeed.

Related Issues

  • Fixes Linear ticket ALL-5247: [Bug]: ModuleNotFoundError: No module named 'fcntl'

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.12-nodejs22 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:f4f28d5-python

Run

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

All tags pushed for this build

ghcr.io/openhands/agent-server:f4f28d5-golang-amd64
ghcr.io/openhands/agent-server:f4f28d5-golang_tag_1.21-bookworm-amd64
ghcr.io/openhands/agent-server:f4f28d5-golang-arm64
ghcr.io/openhands/agent-server:f4f28d5-golang_tag_1.21-bookworm-arm64
ghcr.io/openhands/agent-server:f4f28d5-java-amd64
ghcr.io/openhands/agent-server:f4f28d5-eclipse-temurin_tag_17-jdk-amd64
ghcr.io/openhands/agent-server:f4f28d5-java-arm64
ghcr.io/openhands/agent-server:f4f28d5-eclipse-temurin_tag_17-jdk-arm64
ghcr.io/openhands/agent-server:f4f28d5-python-amd64
ghcr.io/openhands/agent-server:f4f28d5-nikolaik_s_python-nodejs_tag_python3.12-nodejs22-amd64
ghcr.io/openhands/agent-server:f4f28d5-python-arm64
ghcr.io/openhands/agent-server:f4f28d5-nikolaik_s_python-nodejs_tag_python3.12-nodejs22-arm64
ghcr.io/openhands/agent-server:f4f28d5-golang
ghcr.io/openhands/agent-server:f4f28d5-java
ghcr.io/openhands/agent-server:f4f28d5-python

About Multi-Architecture Support

  • Each variant tag (e.g., f4f28d5-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., f4f28d5-python-amd64) are also available if needed

The subprocess_terminal.py and tmux_terminal.py files use Unix-only
modules (fcntl, pty, select) that don't exist on Windows. This caused
a ModuleNotFoundError when importing openhands-tools on Windows.

This fix makes the imports conditional on the platform, only importing
SubprocessTerminal and TmuxTerminal on Unix-like systems.

The factory.py already handles Windows by raising NotImplementedError,
but the import error occurred before the factory was even called.

Fixes: ALL-5247

Co-authored-by: openhands <openhands@all-hands.dev>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 1, 2026

API breakage checks (Griffe)

Result: Failed

Log excerpt (first 1000 characters)

============================================================
Checking openhands-sdk (openhands.sdk)
============================================================
Comparing openhands-sdk 1.11.5 against 1.11.4
::notice title=openhands-sdk API::Ignoring Field metadata-only change (non-breaking): load_public_skills
No breaking changes detected

============================================================
Checking openhands-workspace (openhands.workspace)
============================================================
Comparing openhands-workspace 1.11.5 against 1.11.4
::warning file=openhands-workspace/openhands/workspace/docker/dev_workspace.py,line=33,title=DockerDevWorkspace.server_image::Attribute value was changed: `Field(default='ghcr.io/openhands/agent-server:latest-python', description='Pre-built agent server image to use.')` -> `Field(default=None, description='Pre-built agent server image. Mutually exclusive with base_image.')`
::error title=SemVer::Breaking changes detected (1); re

Action log

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 1, 2026

Agent server REST API breakage checks (OpenAPI)

Result: Passed

Action log

@neubig neubig marked this pull request as ready for review March 1, 2026 13:25
Copy link
Copy Markdown
Collaborator

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

Choose a reason for hiding this comment

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

🟢 Good taste - Pragmatic fix for a real problem.

Linus-Style Analysis:

This is a straightforward solution to a real Windows compatibility issue. The conditional imports based on platform check are simple and direct. The factory already handles Windows by raising NotImplementedError, so this just fixes the import path to not crash before the factory can even run.

VERDICT:
Worth merging - Solves a real user problem without over-engineering.

KEY INSIGHT:
The best fixes are the ones that solve real problems with minimal code changes - this does exactly that.

@neubig neubig marked this pull request as draft March 1, 2026 13:29
@neubig neubig marked this pull request as ready for review March 1, 2026 21:29
Copy link
Copy Markdown
Collaborator

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

Choose a reason for hiding this comment

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

🟡 Acceptable - Pragmatic fix for a real problem.

Linus-Style Analysis:

This solves a real Windows compatibility issue. The conditional imports are simple and direct. Unix behavior is completely unchanged (no benchmark risk). The factory already handles Windows by raising NotImplementedError, so this just fixes the import path to not crash before the factory can even run.

VERDICT:
Worth merging - Solves a real user problem. One maintainability improvement suggested below.

KEY INSIGHT:
The best fixes are simple and fail-fast. This does both - Windows users get a clean ImportError instead of a confusing "module fcntl not found" at import time.

@neubig neubig marked this pull request as draft March 2, 2026 01:52
@neubig neubig marked this pull request as ready for review March 2, 2026 01:58
Copy link
Copy Markdown
Collaborator

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

Choose a reason for hiding this comment

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

🟡 Acceptable - Pragmatic fix for a real Windows compatibility problem.

Linus-Style Analysis:

This solves a real user pain point: Windows users cannot import openhands-tools due to Unix-only modules (fcntl, pty, select). The conditional imports based on platform check are straightforward and direct.

Why this is safe:

  • Unix behavior: UNCHANGED (same imports, same exports, same runtime behavior)
  • Windows behavior: Import succeeds, usage still raises NotImplementedError (as designed by the factory)
  • Benchmark impact: ZERO (evals run on Linux)

The duplicated __all__ lists in if/else blocks violate DRY, but this was already discussed in the resolved thread and is a minor maintainability concern that does not affect correctness.

VERDICT:
Worth merging - Solves a real production issue with minimal, safe changes.

KEY INSIGHT:
The factory pattern already handled Windows correctly; this just ensures the module can be imported before the factory is even called.

@neubig neubig marked this pull request as draft March 2, 2026 12:22
Co-authored-by: openhands <openhands@all-hands.dev>
@neubig neubig marked this pull request as ready for review March 2, 2026 12:46
@neubig neubig marked this pull request as draft March 2, 2026 12:51
@neubig neubig marked this pull request as ready for review March 2, 2026 12:52
Copy link
Copy Markdown
Collaborator

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

Choose a reason for hiding this comment

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

🟢 Good taste - Pragmatic fix for a real problem.

Linus-Style Analysis:

This is a straightforward solution to a real Windows compatibility issue. The conditional imports based on platform check are simple and direct. The factory already handles Windows by raising NotImplementedError, so this just fixes the import path to not crash before the factory can even run.

VERDICT:
Worth merging - Solves a real user problem without over-engineering.

KEY INSIGHT:
The best fixes are the ones that eliminate the problem at the source. Windows users couldn't even import the module before - now they can, and the factory handles the platform limitation gracefully.


⚠️ Review Policy Note:
Per repository policy, this PR touches terminal handling (explicitly listed as requiring human review). While the Unix behavior is completely unchanged (zero benchmark risk) and the Windows fix is low-risk (import succeeds → usage raises NotImplementedError as designed), I'm flagging this for a human maintainer to verify with lightweight evals before merging.

Risk Assessment:

  • Unix systems: ZERO RISK - identical imports, identical exports, identical behavior
  • Windows systems: LOW RISK - import now succeeds (was failing), usage still raises NotImplementedError (intended)
  • Benchmark impact: NONE - Unix behavior unchanged

@neubig neubig closed this Mar 2, 2026
Copy link
Copy Markdown
Contributor Author

neubig commented Mar 2, 2026

Closing in favor of #2096 which addresses the same issue with a more defensive approach (includes explicit ImportError in subprocess_terminal.py and preserves type hints via TYPE_CHECKING). Thank you for the contribution @neubig, but we want to give credit to the community contributor who submitted the fix first.

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.

3 participants