`run_resolved`/`popen_resolved` don't catch PermissionError on Windows — raw traceback in setup, lint, login, deploy, publish, scaffold
- Dominant language
- Python
- Stars
- 5.9k
- Forks
- 660
- PR merge metrics
- No merged PRs in 30d
Description
### What happened?
`agents-cli setup --agent github-copilot -i` crashed with an unhandled `PermissionError: [WinError 5] Access is denied` on Windows, where `uv.exe` is present but blocked from executing by a corporate AppLocker/Defender policy. On Linux, the equivalent situation (uv present but not executable) fails cleanly instead, with `Error: 'uv' is not installed or not on PATH.`
This isn't specific to `setup`. `run_resolved`/`popen_resolved` in `_runner.py` are shared subprocess helpers used by `setup`, `lint`, `auth` (login), `deploy`, `publish`, and `scaffold create/enhance/upgrade`. None of these catch `PermissionError` around the subprocess call, so any of them should crash the same way whenever a resolved tool exists on disk but is blocked from executing.
### Steps to Reproduce
Windows:
1. On a machine where `uv.exe` can't execute at all (even `uv --version` returns Access is denied)
2. Run `agents-cli setup --agent github-copilot -i`
3. Get a raw traceback (see Command Output below)
Linux, for comparison:
1. `chmod -x $(which uv)`
2. Run the same command
3. Get a clean error instead: `Error: 'uv' is not installed or not on PATH.`
### What did you expect to happen?
Same as Linux: a clean, actionable error message instead of a raw Python traceback.
### Client information
(.venv) PS C:\Users\MERTCAN.YAVASOGLU\projects\adk-training> agents-cli info CLI version: 1.2.1 CLI install path: C:\Users\MERTCAN.YAVASOGLU\AppData\Local\Python\pythoncore-3.14-64\Lib\site-packages\google\agents\cli OS info: Windows-11-10.0.22631-SP0 Installed skills: none No agent project found in the current directory or any parent. Run this command from within a project, or create one: agents-cli create my-agent
### Command Output / Logs
Windows:
```
Traceback (most recent call last):
File "...\google\agents\cli\main.py", line 58, in invoke
super().invoke(ctx)
...
File "...\google\agents\cli\_runner.py", line 202, in run_resolved
return subprocess.run(args, **kwargs)
...
PermissionError: [WinError 5] Access is denied
```
Linux (comparison):
```
Error: 'uv' is not installed or not on PATH.
```
### Anything else we need to know?
### Cause
`require_tool()` in `_tools.py` resolves the executable with `shutil.which(name)`. On POSIX, `shutil.which()` checks the execute bit (`os.X_OK`), so a tool that isn't executable resolves to `None` and raises a clean `ToolNotFoundError`. On Windows, `shutil.which()` only checks that the file exists with a valid `PATHEXT` extension — it doesn't check NTFS ACLs or AppLocker/Defender restrictions. So on Windows, `require_tool()` returns a path the OS then refuses to actually launch, and the `PermissionError` from `subprocess.run(args, **kwargs)` in `run_resolved()` (`_runner.py:202`) is never caught. Same for `popen_resolved()`/`popen_resolved_detached()`.
Confirmed by reading `_runner.py`/`_tools.py` in the installed package (v1.2.1).
### Affected commands
Grepping for `run_resolved`/`popen_resolved` call sites:
- `setup/_antigravity.py`
- `dev/cmd_lint.py` (`uv run ruff/mypy/ty/codespell`)
- `auth.py` (login, 3 call sites)
- `deploy/cmd_deploy.py`
- `publish/cmd_publish.py`
- `scaffold/utils/*` and `scaffold/commands/enhance.py` (create/enhance/upgrade)
- `infra/_cicd_utils.py`
Worth noting: `_skills_check.py`'s `get_installed_skills()` already wraps its `run_resolved` call in a broad `except Exception` and degrades gracefully — that's the pattern the call sites above are missing.
### Suggested fix
```diff
def run_resolved(
args: list[str], *, resolve_executable: bool = True, **kwargs
) -> subprocess.CompletedProcess:
if isinstance(args, str):
raise ValueError("args must be a list of strings, not a single string.")
if resolve_executable and args:
executable = args[0]
args = args.copy()
args[0] = _tools.require_tool(executable)
- return subprocess.run(args, **kwargs)
+ try:
+ return subprocess.run(args, **kwargs)
+ except PermissionError as e:
+ raise click.ClickException(
+ f"'{args[0]}' was found but could not be executed (Access is denied). "
+ "This is often caused by AppLocker, Windows Defender Application "
+ "Control, or antivirus software blocking the executable."
+ ) from e
```
Same idea applies to `popen_resolved`/`popen_resolved_detached`.
### Related
Native Windows isn't officially supported (docs list macOS/Linux/WSL2), so this isn't a request for Windows support — just for the failure to surface cleanly instead of a raw traceback, as it already does on Linux.
Contributor guide
Research direction
Start in _runner.py with run_resolved, popen_resolved, and popen_resolved_detached, then read _tools.py to understand executable resolution. Compare the graceful exception handling in _skills_check.py and trace the listed call sites; done means blocked executables produce a clean actionable error rather than a raw traceback.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- cli, operating-systems
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100