Skip to content

fix(computer): guard Unix-only pwd import in site_proxy - #519

Merged
earakely-scale merged 4 commits into
scaleapi:mainfrom
saiyam-jain-dev:fix/site-proxy-pwd-import
Nov 9, 2026
Merged

earakely-scale merged 4 commits into
scaleapi:mainfrom
saiyam-jain-dev:fix/site-proxy-pwd-import

Conversation

@saiyam-jain-dev

@saiyam-jain-dev saiyam-jain-dev commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

What and why

On Windows, agent-env couldn't even start: every command, --help included, failed with ModuleNotFoundError: No module named 'pwd'. site_proxy.py imported pwd, which is Unix-only, at module level. The host imports it through sites.py, which takes is_local and matches, and sites.py is imported by the bundle planner and the built-in website and multi-server envs.

This change guards that one import (pwd = None on Windows), as bundle/_fs.py already does for its own pwd and fcntl. Nothing else changes: serve, drop_privileges, install and trust_nss run only as root inside the Linux computer, where pwd exists, and still fail loudly as before. The local object store already skips fcntl on Windows, so it needs no change.

is_local and matches stay in site_proxy.py. The proxy runs inside the computer as one standalone file on the base's own python3 and uses both itself, so moving them would mean keeping two copies.

This makes agent-env importable on Windows. It doesn't make Windows a supported host: pyproject.toml lists macOS and Linux.

How it was tested

  • On Windows 11, with agentenv-framework 0.9.1474 installed in a venv:
    • as released, agent-env --help, config show, env --help and run --help all exited 1 on No module named 'pwd', and so did importing agent_env.cli, sites, bundle.plan, the website and multi-server envs, and run_code;
    • with only this guard, every one of those imports and commands worked;
    • the local object store imported in both cases.
  • Unit: test_the_proxy_imports_when_pwd_is_unavailable imports site_proxy and sites in a fresh interpreter with pwd blocked. It fails against main and passes with the guard. tst/unit/env/envs: 829 passed. Ruff is clean.

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge; no actionable new issues were found.

Summary

Guards the pwd import so host-side helpers can load without that Unix-only module.

  • Site-routing helpers load when Unix pwd is unavailable.

Reviews (4) · Last reviewed commit: "Merge main; keep only the guard on site_..." · Reviewed by Greptile

@saiyam-jain-dev
saiyam-jain-dev requested a review from a team as a code owner November 6, 2026 22:21
@saiyam-jain-dev

Copy link
Copy Markdown
Contributor Author

Added a fresh-import test (test_the_proxy_imports_when_pwd_is_unavailable) that blocks pwd in sys.modules and reloads site_proxy to verify it imports cleanly without the module.

@andrewpark-scaleai andrewpark-scaleai 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.

Requesting changes, mainly because this doesn't achieve its stated goal.

It still doesn't import on non-Unix. I blocked every Unix-only module (pwd, fcntl, grp, termios, resource, pty, tty) and imported agent_env.env.envs.computer.utils.sites and computer.env at this head. Both still fail at src/agent_env/store/object_store/local/store.py:27, which does an unconditional import fcntl. The PR only gets past pwd, so the same import chain still breaks on Windows. bundle/_fs.py:18 also says agent-env doesn't target Windows. Which platform hit this? If there is a real non-Unix caller, fcntl needs a guard too. If there isn't one, I'd close this.

Keep the change to the import itself. serve, drop_privileges, install and trust_nss only run as root inside the Linux computer, on the base's own python3. The only host-side import of this module is sites.py:36, which takes is_local and matches. A try/except around import pwd, or moving those two functions into a module with no pwd dependency, covers the host case. That makes the runtime guards unnecessary. Details are inline.

os.setgroups([])
os.setgid(entry.pw_gid)
os.setuid(entry.pw_uid)
if hasattr(os, "setgroups"):

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.

These hasattr checks can't be false. This branch is only reachable after os.geteuid() == 0, which only happens on a Unix host where setgroups, setgid and setuid all exist. And if one of them ever were missing, the privilege drop would be silently skipped while the code carried on as root. A privilege drop shouldn't fail that way. Please revert to the unconditional calls.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Reverted to unconditional calls.

return []
try:
home = Path(pwd.getpwnam(user).pw_dir)
except KeyError:

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.

This changes behavior on Linux, and the description doesn't mention it. Before, an unknown session user raised KeyError and install failed loudly. Now it returns [], so a misnamed user silently skips NSS trust, and the first symptom is a certificate error in Chromium much later. The pwd is None check above is enough for the import case. Please drop this except KeyError.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Dropped the KeyError exception handler so missing session users fail loudly as before.

monkeypatch.setattr(site_proxy, "pwd", None)
assert site_proxy.serve_user("agent") == "agent"
assert site_proxy.trust_nss("agent", "/fake/ca.pem") == []
site_proxy.drop_privileges("agent")

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.

Nothing is asserted after this call, so the test only shows that it doesn't raise. If the hasattr guards are removed as suggested above, this line goes too.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed along with the runtime guards.

site_proxy.drop_privileges("agent")


def test_the_proxy_imports_when_pwd_is_unavailable(monkeypatch):

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.

Reloading site_proxy partway through the suite swaps out the module's classes. After the reload, site_proxy.Config is not the Config this file imported at the top. I saw no failures locally (the file and the rest of tst/unit/env/envs pass), but this is fragile. If the import guard is the only change left, running python -c in a subprocess with sys.modules['pwd'] = None tests the fresh import without reloading the module in the test process.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated to run the fresh import assertion in an isolated subprocess (python -c) without in-process reloads.

@saiyam-jain-dev

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed review and guidance @andrewpark-scaleai.

Updated the branch with the following changes:

  1. Reverted all runtime guards in drop_privileges, trust_nss, as_user, install, and open_log back to their original implementations, preserving unconditional privilege drop behavior and explicit failure on unknown users.
  2. In site_proxy.py, kept only the import fallback (try ... except ImportError: pwd = None) and updated serve_user to catch (KeyError, AttributeError) when pwd is absent.
  3. Swapped store.py:26 from if os.name != "nt": import fcntl to try: import fcntl except ImportError: fcntl = None so downstream imports through agent_env.store do not fail when fcntl is unavailable or blocked.
  4. Updated test_the_proxy_imports_when_pwd_is_unavailable to run in an isolated subprocess (python -c) without reloading site_proxy in-process, and removed the redundant fallback test.

The local store already skips fcntl on Windows, and serve_user runs only as root inside a Linux computer, where pwd
exists, so both go back to main's code. The test imports sites, the host's import of the proxy, with pwd blocked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale

Copy link
Copy Markdown
Collaborator

On which platform hit this: a native Windows host. On Windows 11 with 0.9.1474, every agent-env command, --help included, fails on import with No module named 'pwd', through sites.py → site_proxy.py. With only the guard on that import, the CLI and every core module I tried import and run.

On fcntl: on real Windows the local store imports fine, since its if os.name != "nt" guard skips fcntl there. The failure in the review came from blocking fcntl on Linux, where that guard doesn't apply. So the store change isn't needed.

What I pushed (c099ad5, a merge of main with no force-push):

  • conflicts with main resolved;
  • the PR cut to the guard on pwd, with serve_user and the local store back to main's code;
  • the test now also imports sites, the host's path to the proxy, with pwd blocked. It fails against main and passes with the guard.

The description is updated to match, including that this makes agent-env importable on Windows, not a supported host.

@earakely-scale
earakely-scale merged commit 70dbff7 into scaleapi:main Nov 9, 2026
33 of 36 checks passed
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