Skip to content

fix(arcade-cli): resolve org/project coordinator URL from arcade login - #912

Draft
HBX814 wants to merge 1 commit into
ArcadeAI:mainfrom
HBX814:fix/cli-coordinator-url-resolution
Draft

fix(arcade-cli): resolve org/project coordinator URL from arcade login#912
HBX814 wants to merge 1 commit into
ArcadeAI:mainfrom
HBX814:fix/cli-coordinator-url-resolution

Conversation

@HBX814

@HBX814 HBX814 commented Aug 22, 2026

Copy link
Copy Markdown

Summary

arcade login -h <coordinator-host> saves the coordinator it authenticated against, and the token it saves is only valid for that coordinator. The org and project command groups, however, built their request URL from PROD_COORDINATOR_HOST, while get_auth_headers already fell back to the saved config.coordinator_url for the token. On a dedicated instance the two halves of the request disagreed: the call went to https://cloud.arcade.dev carrying a token minted by the tenant coordinator, and came back 401 Unauthorized.

That made the failure land in a bad spot — arcade project list is the command the login success message itself suggests, so the first thing a user does after a successful login fails.

This resolves the group URL as explicit flags → coordinator saved by arcade login → production, so the token and the request URL always name the same environment.

Resolves: #896

Design decisions

Resolution lives in one shared helper. resolve_coordinator_url in arcade_cli/utils.py sits next to get_auth_headers, which already does the token half of the same resolution. Keeping them adjacent is the point: these two must agree, and the bug was precisely that they didn't.

Explicit flags stay authoritative, and that includes --port/--tls/--no-tls, not just --host. The saved coordinator is a full URL, so honoring it while applying a partial override would mean guessing which half wins (does the saved :8443 survive a bare --tls?). Rather than invent that precedence, any explicit connection flag means the caller is describing the target and the URL is built from the flags alone. This also keeps behavior byte-identical for anyone currently passing flags, including the arcade project -h <host> list workaround from the issue.

The saved coordinator is used verbatim, preserving its scheme and port, rather than being decomposed into a host and rebuilt.

--host now defaults to None instead of PROD_COORDINATOR_HOST. With the old default, a user explicitly typing -h cloud.arcade.dev was indistinguishable from not passing the flag, so "was this overridden?" could not be answered. None makes that distinction real.

Reads go through a small _coordinator_url() accessor. The module-level state dict previously held a hardcoded production URL that the group callback overwrote on every real invocation. Seeding it with production meant that if the callback were ever bypassed, the fallback would be the exact silent-401 behavior being fixed here. The accessor re-resolves instead, so the correct behavior is also the fallback.

Failure to read the config degrades to None, not an exception. Importing arcade_core.config loads the credentials file at import time and raises when logged out, so get_saved_coordinator_url swallows that and lets callers fall back to production. A logged-out user gets today's behavior.

Scope

In scope:

Not in scope (deliberately):

  • secret.py, which the issue flags for the same audit. It has the same shape of bug, but it is engine-scoped (PROD_ENGINE_HOST), not coordinator-scoped, so fixing it needs the cloud.*api.* derivation added in fix(arcade-mcp-server): derive engine URL from arcade login coordinator #892 — which currently lives in arcade_mcp_server/server.py as _engine_url_from_coordinator. Doing it properly means promoting that helper into arcade-core so the CLI and the MCP server share one implementation instead of duplicating it. That is a wider refactor across two packages and reads better as its own PR. Happy to follow up with it if you want it.
  • connect.py already resolves coordinator_url or f"https://{PROD_COORDINATOR_HOST}", so it is unaffected.

Test plan

New libs/tests/cli/test_coordinator_url.py (20 tests) covering the resolver, the config read, both command groups end-to-end through Typer (list and set), and the accessor fallback.

  • Confirmed the new tests actually reproduce the bug. Reverting only project.py/org.py to the original code fails 4 of them with Actual: fetch_projects('https://cloud.arcade.dev', 'org_1') — the exact URL from the issue. They pass with the fix.

  • Exercised the real end-user path with no mocks, via a genuine post-login credentials.yaml (written through Config.save_to_file) and ARCADE_WORK_DIR:

    project list targets org list targets
    before https://cloud.arcade.dev (→ 401) https://cloud.arcade.dev (→ 401)
    after https://coordinator.tenant.example.com https://coordinator.tenant.example.com

    And arcade project -h override.example.com list still targets https://override.example.com, so the issue's workaround keeps working.

  • Logged-out / unreadable-config case falls back to https://cloud.arcade.dev without raising.

  • libs/tests/cli/ green: 806 passed, 45 skipped (786 before this change, +20 new).

  • Codecov flagged 2 uncovered patch lines on the first run: the _coordinator_url() call sites inside arcade project set / arcade org set, which the original tests did not reach. Those subcommands resolve the Coordinator the same way list does, so they are now covered too — a real gap, not a coverage-chasing edit.

  • ruff check, ruff format --check, and pre-commit run --files <changed> clean on the changed files; mypy clean on arcade-cli (29 files).

  • Full libs/tests/ run: 3218 passed, 532 skipped. The 2 failures in arcade_mcp_server/integration/test_end_to_end.py (test_stdio_e2e, test_http_e2e) are pre-existing on main — verified by stashing this change and re-running — and are a local server-spawn/port issue on Windows, unrelated to arcade-cli.

No version bump: behavior-only fix, no public API or dependency change.

Risk note

Behavior changes only for users who pass no connection flags to arcade org / arcade project:

  • Logged in to production → unchanged (cloud.arcade.dev either way).
  • Logged in to a dedicated/staging coordinator → now targets that coordinator instead of returning 401. This is the fix.
  • Logged out, or credentials unreadable → unchanged (production).

Anyone passing --host/--port/--tls/--no-tls is unaffected by construction. The one way this could surprise someone is a stale coordinator_url in credentials.yaml from an old login, which would now be honored where production was used before; arcade logout && arcade login, or an explicit -h, resolves that.

@HBX814
HBX814 force-pushed the fix/cli-coordinator-url-resolution branch from 94a487d to 7af7b3f Compare August 22, 2026 12:02
@codecov

codecov Bot commented Aug 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.85714% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
libs/arcade-cli/arcade_cli/org.py 85.71% 1 Missing ⚠️
libs/arcade-cli/arcade_cli/project.py 85.71% 1 Missing ⚠️
Files with missing lines Coverage Δ
libs/arcade-cli/arcade_cli/utils.py 58.15% <100.00%> (+1.09%) ⬆️
libs/arcade-cli/arcade_cli/org.py 36.76% <85.71%> (+15.86%) ⬆️
libs/arcade-cli/arcade_cli/project.py 40.29% <85.71%> (+19.08%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

The `org` and `project` command groups built their coordinator URL from
`PROD_COORDINATOR_HOST`, while `get_auth_headers` resolved the token from
the `coordinator_url` saved by `arcade login`. On a dedicated instance the
two disagreed: the request went to `https://cloud.arcade.dev` carrying a
token minted by the tenant coordinator, so `arcade project list` failed
with 401 immediately after a successful login — and it is the very command
the login success message suggests.

Resolve the group URL as explicit flags, then the saved coordinator, then
production, mirroring the precedence introduced for the engine URL in ArcadeAI#892
so the token and the request URL always name the same environment.

Resolves: ArcadeAI#896
@HBX814
HBX814 force-pushed the fix/cli-coordinator-url-resolution branch from 7af7b3f to eedd182 Compare August 24, 2026 20:27
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.

[Bug] CLI: org/project commands ignore saved coordinator host and default to Arcade Cloud

1 participant