Skip to content

bug(installer): PermissionError in _is_custom_agent_dir crashes Export on Windows (Python 3.12.x / restricted NTFS ACLs) #830

@itomek

Description

@itomek

Summary

On Windows with Python 3.12.x, Path.is_file() raises PermissionError (WinError 5: Access is denied) instead of returning False when called on paths inside directories that have restricted NTFS ACLs. This causes export_custom_agents() to crash with an unhandled exception, and the POST /api/agents/export endpoint returns HTTP 500 to the UI, which shows an error banner.

Affected file: src/gaia/installer/export_import.py
Affected function: _is_custom_agent_dir()

Root Cause

# BEFORE (buggy)
def _is_custom_agent_dir(path: Path) -> bool:
    return path.is_dir() and (
        (path / "agent.py").is_file() or (path / "agent.yaml").is_file()
    )

pathlib.Path.is_file() is documented to return False on errors, but on Windows with Python 3.12.12 it raises PermissionError for NTFS paths where the caller lacks read permission. This is a known CPython regression/platform inconsistency. Any subdirectory of ~/.gaia/agents/ with restricted permissions (e.g. a symlink or directory created by another process with locked-down ACLs) triggers the crash.

Reproduction Steps

  1. Install GAIA v0.17.2 on Windows 11 with Python 3.12.x
  2. Create a directory with restricted NTFS permissions inside ~/.gaia/agents/:
    $dir = "$env:USERPROFILE\.gaia\agents\restricted-dir"
    New-Item -ItemType Directory -Path $dir
    $acl = Get-Acl $dir
    $acl.SetAccessRuleProtection($true, $false)
    Set-Acl -Path $dir -AclObject $acl
  3. Open the GAIA Agent UI desktop app
  4. Go to Settings → Custom Agents → Export All
  5. Click through the security confirmation dialog
  6. Choose a save path and click Save

Expected: Export succeeds, skipping or ignoring the restricted directory
Actual: PermissionError: [WinError 5] Access is denied is raised; UI shows an error banner; no zip file is produced

Fix

Wrap the file checks in a try/except OSError block:

# AFTER (fixed)
def _is_custom_agent_dir(path: Path) -> bool:
    """A directory qualifies as a custom agent if it holds agent.py or agent.yaml."""
    try:
        return path.is_dir() and (
            (path / "agent.py").is_file() or (path / "agent.yaml").is_file()
        )
    except OSError:
        return False

This matches the documented intent of is_file() (return False on errors) and silently skips any unreadable subdirectory rather than crashing the entire export.

Acceptance Criteria

  • _is_custom_agent_dir() does not raise when called on a path inside ~/.gaia/agents/ that has restricted NTFS permissions
  • Export succeeds (produces a valid zip) when ~/.gaia/agents/ contains a mix of valid agent directories and unreadable/restricted subdirectories
  • Restricted/unreadable directories are silently skipped — they do not appear in bundle.json agent_ids
  • Unit test added: mock Path.is_file() to raise PermissionError; assert _is_custom_agent_dir() returns False rather than propagating the exception
  • Behavior is consistent on Windows (Python 3.12.x) and Linux/macOS

Environment

OS Windows 11
Python 3.12.12
GAIA version 0.17.2
Hardware AMD Ryzen AI MAX+ 395

References

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions