mirror of
https://github.com/run-llama/workflows-py.git
synced 2026-07-20 19:57:02 -04:00
fix: projects use/get cross-org lookup (#638)
Tab completion lists projects from all orgs the user can access, but `projects use <id>` validated against only the default org. A project from a non-default org would tab-complete fine then fail with "Project not found". The issue is `_discover_organization` running before the direct-ID path, scoping the validation query to whichever org has `is_default=True`. Moved org discovery to the interactive listing branch only. When the user passes a project ID directly, validation now queries all accessible orgs (unless they explicitly passed `--org`). Same fix applied to `projects get <id>`.
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"llamactl": patch
|
||||
---
|
||||
|
||||
Fix `projects use <id>` and `projects get <id>` failing with "not found" for projects outside the default org
|
||||
@@ -44,18 +44,23 @@ def get_projects(project_id: str | None, org_id: str | None, output: str) -> Non
|
||||
try:
|
||||
auth_svc = _get_service().current_auth_service()
|
||||
profile = validate_authenticated_profile()
|
||||
if org_id is None:
|
||||
org = _discover_organization(auth_svc)
|
||||
if org is not None:
|
||||
org_id = org.org_id
|
||||
|
||||
projects = _list_projects(auth_svc, org_id=org_id)
|
||||
if project_id:
|
||||
# Look up a specific project across all accessible orgs,
|
||||
# unless the user explicitly scoped with --org.
|
||||
projects = _list_projects(auth_svc, org_id=org_id)
|
||||
projects = [
|
||||
project for project in projects if project.project_id == project_id
|
||||
]
|
||||
if not projects:
|
||||
raise click.ClickException(f"Project {project_id} not found")
|
||||
else:
|
||||
# Scope the listing to the default org when --org is not given.
|
||||
if org_id is None:
|
||||
org = _discover_organization(auth_svc)
|
||||
if org is not None:
|
||||
org_id = org.org_id
|
||||
projects = _list_projects(auth_svc, org_id=org_id)
|
||||
|
||||
if not projects and output == "text":
|
||||
status("no projects found")
|
||||
@@ -93,16 +98,13 @@ def use_project(project_id: str | None, org_id: str | None) -> None:
|
||||
profile = validate_authenticated_profile()
|
||||
|
||||
try:
|
||||
if org_id is None:
|
||||
org = _discover_organization(auth_svc)
|
||||
if org is not None:
|
||||
org_id = org.org_id
|
||||
|
||||
if project_id and profile.project_id == project_id:
|
||||
return
|
||||
|
||||
if project_id:
|
||||
if auth_svc.env.requires_auth:
|
||||
# Validate across all accessible orgs (matching tab completion),
|
||||
# unless the user explicitly scoped with --org.
|
||||
projects = _list_projects(auth_svc, org_id=org_id)
|
||||
if not next(
|
||||
(
|
||||
@@ -117,6 +119,11 @@ def use_project(project_id: str | None, org_id: str | None) -> None:
|
||||
status(f"switched project {project_id}")
|
||||
return
|
||||
|
||||
# Scope the interactive listing to the default org when --org is not given.
|
||||
if org_id is None:
|
||||
org = _discover_organization(auth_svc)
|
||||
if org is not None:
|
||||
org_id = org.org_id
|
||||
projects = _list_projects(auth_svc, org_id=org_id)
|
||||
|
||||
if not projects:
|
||||
|
||||
@@ -181,6 +181,42 @@ def test_auth_logout_missing() -> None:
|
||||
assert "Profile 'missing' not found" in result.output
|
||||
|
||||
|
||||
def test_projects_use_validates_across_all_orgs() -> None:
|
||||
"""projects use <id> should not scope validation to the default org.
|
||||
|
||||
Tab completion lists projects from all orgs, so the validation path
|
||||
must also search all orgs — otherwise a project from a non-default org
|
||||
completes but then fails with 'Project not found'.
|
||||
"""
|
||||
runner = CliRunner()
|
||||
cross_org_project = MagicMock(project_id="cross-org-proj-id")
|
||||
with (
|
||||
patch(
|
||||
"llama_agents.cli.commands.projects.validate_authenticated_profile",
|
||||
return_value=MagicMock(name="p", project_id="current-proj"),
|
||||
),
|
||||
patch("llama_agents.cli.config.env_service.service") as mock_service,
|
||||
patch(
|
||||
"llama_agents.cli.commands.projects._list_projects",
|
||||
return_value=[cross_org_project],
|
||||
) as mock_list,
|
||||
patch(
|
||||
"llama_agents.cli.commands.projects._discover_organization",
|
||||
return_value=MagicMock(org_id="default-org-id"),
|
||||
),
|
||||
):
|
||||
mock_auth_svc = MagicMock()
|
||||
mock_auth_svc.env.requires_auth = True
|
||||
mock_service.current_auth_service.return_value = mock_auth_svc
|
||||
|
||||
result = runner.invoke(app, ["projects", "use", "cross-org-proj-id"])
|
||||
assert result.exit_code == 0
|
||||
# Must call _list_projects without org scoping (org_id=None),
|
||||
# not with the auto-discovered default org.
|
||||
mock_list.assert_called_once_with(mock_auth_svc, org_id=None)
|
||||
mock_auth_svc.set_project.assert_called_once()
|
||||
|
||||
|
||||
def test_projects_use_non_interactive_lists_options_and_hints() -> None:
|
||||
runner = CliRunner()
|
||||
with (
|
||||
|
||||
Reference in New Issue
Block a user