Description
On Windows, --artifact_service_uri with a canonical local file URI such as file:///C:/... is parsed incorrectly for FileArtifactService. The URI path is passed directly from urlparse(uri).path into Path(...) without Windows URI-to-filesystem normalization. As a result, the artifact root is created in the wrong location, with the path duplicated under the current working directory.
Steps to Reproduce:
- Install
google-adk on Windows.
- Use any valid ADK app directory, for example, a minimal single-agent app directory, as
[AGENTS_DIR].
- Start the ADK API server from a project directory with:
adk api_server `
--artifact_service_uri "file:///C:/Users/user1/projects/agent1/runtime/artifacts" `
[AGENTS_DIR]
- Inspect the created artifact directory on disk.
Expected Behavior:
The artifact root should be created at:
C:\Users\user1\projects\agent1\runtime\artifacts
Observed Behavior:
The artifact root is created in the wrong location:
C:\Users\user1\projects\agent1\Users\user1\projects\agent1\runtime\artifacts
I traced this to google/adk/cli/service_registry.py, where file_artifact_factory() does:
def file_artifact_factory(uri: str, **_):
from ..artifacts.file_artifact_service import FileArtifactService
parsed_uri = urlparse(uri)
if parsed_uri.netloc not in ("", "localhost"):
raise ValueError(
"file:// artifact URIs must reference the local filesystem."
)
if not parsed_uri.path:
raise ValueError("file:// artifact URIs must include a path component.")
artifact_path = Path(unquote(parsed_uri.path))
return FileArtifactService(root_dir=artifact_path)
On Windows, parsed_uri.path for file:///C:/... is still a file-URI path, not a native Windows filesystem path, but it needs Windows-specific normalization before constructing Path(...).
I locally patched it with:
from urllib.request import url2pathname
import os
def file_artifact_factory(uri: str, **_):
from ..artifacts.file_artifact_service import FileArtifactService
parsed_uri = urlparse(uri)
if parsed_uri.netloc not in ("", "localhost"):
raise ValueError(
"file:// artifact URIs must reference the local filesystem."
)
if not parsed_uri.path:
raise ValueError("file:// artifact URIs must include a path component.")
artifact_path_str = unquote(parsed_uri.path)
if os.name == "nt":
artifact_path_str = url2pathname(artifact_path_str)
artifact_path = Path(artifact_path_str)
return FileArtifactService(root_dir=artifact_path)
After this patch, the artifact root is created in the correct directory.
Environment Details:
ADK Library Version (pip show google-adk): 1.31.1
Desktop OS: Windows 11
Python Version (python -V): 3.13.4
Model Information:
Are you using LiteLLM: No
Which model is being used: gemini-2.5-flash-lite
Additional Context:
This seems to be a Windows-specific URI-to-path conversion issue in the file_artifact_factory(), not in FileArtifactService itself.
I also noticed a workaround-like behavior: if the drive prefix is omitted and a non-canonical path form is used (e.g., instead of file:///C:/User/..., use this file:///User/...), the current code may appear to work because resolve() in the following snippet from the FileArtifactService class, which resides in google/adk/artifacts/file_artifact_service.py, reconstructs a drive-rooted path. However, I do not think that should be considered the supported or correct behavior. The canonical Windows file URI form file:///C:/... should work.
class FileArtifactService(BaseArtifactService):
"""Stores filesystem-backed artifacts beneath a configurable root directory."""
# Storage layout matches the cloud and in-memory services:
# root/
# └── users/
# └── {user_id}/
# ├── sessions/
# │ └── {session_id}/
# │ └── artifacts/
# │ └── {artifact_path}/ # derived from filename
# │ └── versions/
# │ └── {version}/
# │ ├── {original_filename}
# │ └── metadata.json
# └── artifacts/
# └── {artifact_path}/...
#
# Artifact paths are derived from the provided filenames: separators create
# nested directories, and path traversal is rejected to keep the layout
# portable across filesystems. `{artifact_path}` therefore mirrors the
# sanitized, scope-relative path derived from each filename.
def __init__(self, root_dir: Path | str):
"""Initializes the file-based artifact service.
Args:
root_dir: The directory that will contain artifact data.
"""
self.root_dir = Path(root_dir).expanduser().resolve()
self.root_dir.mkdir(parents=True, exist_ok=True)
Minimal Reproduction Code:
from urllib.parse import urlparse, unquote
from pathlib import Path
uri = "file:///C:/Users/user1/projects/agent1/runtime/artifacts"
parsed_uri = urlparse(uri)
print(parsed_uri.path) # Output-> /C:/Users/user1/projects/agent1/runtime/artifacts
print(Path(unquote(parsed_uri.path)).resolve()) # Output-> C:Users\user1\projects\agent1\runtime\artifacts
On Windows, this shows that the URI path is not being normalized to a proper native filesystem path before Path(...) is used.
Description
On Windows,
--artifact_service_uriwith a canonical local file URI such asfile:///C:/...is parsed incorrectly forFileArtifactService. The URI path is passed directly fromurlparse(uri).pathintoPath(...)without Windows URI-to-filesystem normalization. As a result, the artifact root is created in the wrong location, with the path duplicated under the current working directory.Steps to Reproduce:
google-adkon Windows.[AGENTS_DIR].Expected Behavior:
The artifact root should be created at:
C:\Users\user1\projects\agent1\runtime\artifactsObserved Behavior:
The artifact root is created in the wrong location:
C:\Users\user1\projects\agent1\Users\user1\projects\agent1\runtime\artifactsI traced this to
google/adk/cli/service_registry.py, wherefile_artifact_factory()does:On Windows,
parsed_uri.pathforfile:///C:/...is still a file-URI path, not a native Windows filesystem path, but it needs Windows-specific normalization before constructingPath(...).I locally patched it with:
After this patch, the artifact root is created in the correct directory.
Environment Details:
ADK Library Version (pip show google-adk): 1.31.1
Desktop OS: Windows 11
Python Version (python -V): 3.13.4
Model Information:
Are you using LiteLLM: No
Which model is being used: gemini-2.5-flash-lite
Additional Context:
This seems to be a Windows-specific URI-to-path conversion issue in the
file_artifact_factory(), not inFileArtifactServiceitself.I also noticed a workaround-like behavior: if the drive prefix is omitted and a non-canonical path form is used (e.g., instead of
file:///C:/User/..., use thisfile:///User/...), the current code may appear to work becauseresolve()in the following snippet from theFileArtifactServiceclass, which resides ingoogle/adk/artifacts/file_artifact_service.py, reconstructs a drive-rooted path. However, I do not think that should be considered the supported or correct behavior. The canonical Windows file URI formfile:///C:/...should work.Minimal Reproduction Code:
On Windows, this shows that the URI path is not being normalized to a proper native filesystem path before
Path(...)is used.