Skip to content

Feat/cli auth consistency#878

Merged
PythonFZ merged 17 commits intomainfrom
feat/cli-auth-consistency
Mar 19, 2026
Merged

Feat/cli auth consistency#878
PythonFZ merged 17 commits intomainfrom
feat/cli-auth-consistency

Conversation

@PythonFZ
Copy link
Copy Markdown
Member

@PythonFZ PythonFZ commented Mar 19, 2026

Summary by CodeRabbit

  • New Features

    • Added explicit --user and --password options across CLI commands and auth status; authentication now accepts user/password or token.
  • Bug Fixes

    • Stronger credential validation and clearer auth error handling; supplying a user without a password now errors.
  • Documentation

    • New design/plan docs for unified CLI authentication and migration steps.
  • Tests

    • Updated and added tests covering token resolution, credential validation, deprecation behavior, and auth fallbacks.
  • Chores

    • Renamed env var ZNDRAW_EMAIL → ZNDRAW_USER; legacy usage emits a deprecation warning.

PythonFZ and others added 11 commits March 19, 2026 14:49
Unify CLI authentication by making --user/--password explicit Typer
flags and extracting resolve_token() into a shared utility.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix signature (base_url instead of httpx.Client), document resolution
chain precedence rationale, add auth status no-credentials behavior,
fix Docker paths, add SecretStr support, include list_rooms/login
class methods, add ZNDRAW_EMAIL migration warning, and clarify test
rewrite scope.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
7 tasks: shared auth_utils, connection.py refactor, auth status update,
all subcommand signatures, ZnDraw client integration, Docker/docs
env var rename, and final verification.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
TDD: 12 tests covering validation (mutual exclusion, partial combos,
SecretStr), resolution chain (explicit, stored, guest), login failure,
and ZNDRAW_EMAIL migration warning.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…wrapper

Add UserOpt/PasswordOpt option types, update get_connection/get_zndraw
signatures, remove os import (env var handling moved to auth_utils).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Supports login validation with mutual exclusion. No guest fallback.
Reports token_source as "login" for user/password auth.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Mechanical update of ~60 command functions across 18 files.
Every command now shows --user and --password in --help output.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ed_token

Replace bespoke auth chain in __post_init__ and ad-hoc auth in
list_rooms()/login() with shared auth_utils.resolve_token().

Breaking: ZnDraw(user="x") without password now raises ValueError
instead of falling back to Settings().guest_password.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update docker-compose files and SKILL.md to reflect new env var name.
auth_utils.py emits a DeprecationWarning for old ZNDRAW_EMAIL usage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…_task

- Update test_client_integration: pass password explicitly (no more
  implicit Settings().guest_password fallback)
- Remove test_client_token_discovery: tested _try_stored_token which
  no longer exists (covered by test_resolve_token.py)
- Fix _execute_task: pass token to child ZnDraw, skip /users/me lookup
  when token is explicit to avoid extra HTTP round-trip

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 19, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1bca54fd-d76f-4959-b864-c0f5829ede96

📥 Commits

Reviewing files that changed from the base of the PR and between 9fe8857 and 020682a.

📒 Files selected for processing (1)
  • src/zndraw/cli_agent/connection.py

📝 Walkthrough

Walkthrough

Centralizes authentication via a new src/zndraw/auth_utils.resolve_token() (explicit token, user/password login, stored-token validation, guest fallback), threads new --user/--password Typer options through CLI modules and client code, and renames Docker env var ZNDRAW_EMAILZNDRAW_USER.

Changes

Cohort / File(s) Summary
Docker Compose
docker/production/docker-compose.yaml, docker/standalone/docker-compose.yaml
Renamed template-loader env var ZNDRAW_EMAILZNDRAW_USER.
Auth utility
src/zndraw/auth_utils.py
Added get_token_store(), validate_credentials(), and resolve_token(base_url, token=None, user=None, password=None) implementing explicit token, explicit user/password login (no persistence), stored-token validation (delete on 401/403), guest POST fallback, and ZNDRAW_EMAILZNDRAW_USER deprecation bridging.
CLI connection surface
src/zndraw/cli_agent/connection.py
Replaced in-file token-discovery with a wrapper delegating to auth_utils.resolve_token(); added UserOpt/PasswordOpt Typer options; updated resolve_token(), get_connection(), get_zndraw() signatures to accept user/password; consolidated exception→exit mapping; removed local TokenStore/httpx logic.
CLI commands (threading creds)
src/zndraw/cli_agent/*.py (admin, auth, bookmarks, chat, extensions, figures, frames, geometries, gif, jobs, mount, presets, rooms, screenshots, selection, selection_groups, sessions, step)
Added user: UserOpt and password: PasswordOpt options across many CLI modules and updated calls to resolve_token(), get_connection(), and get_zndraw() to pass user/password.
Python client
src/zndraw/client/core.py
Refactored to call auth_utils.resolve_token() in __post_init__, list_rooms(), and login(); removed _try_stored_token() and local httpx/token-store verification; task/worker wiring now uses token.
Docs & planning
docs/superpowers/..., skills/zndraw/SKILL.md
Added design and plan docs describing the shared resolve_token() and CLI/auth changes; updated skill doc to reflect ZNDRAW_USER/--user priority and removal of legacy ZNDRAW_EMAIL step.
Tests (updated)
tests/test_resolve_token.py, tests/test_client_integration.py
Re-targeted tests to zndraw.auth_utils.resolve_token(), added validation tests (invalid flag combos, SecretStr unwrapping), updated integration tests to require explicit password with user, and adjusted expected exceptions.
Tests (deleted)
tests/test_client_token_discovery.py
Removed tests that exercised the old in-module stored-token discovery path.

Sequence Diagram(s)

sequenceDiagram
    actor CLI as CLI / Client
    participant Resolve as resolve_token()
    participant Store as TokenStore
    participant Server as Server API

    CLI->>Resolve: resolve_token(base_url, token?, user?, password?)
    alt explicit token provided
        Resolve-->>CLI: return token
    else user & password provided
        Resolve->>Server: POST /v1/auth/jwt/login {user,password}
        Server-->>Resolve: {access_token}
        Resolve-->>CLI: return access_token
    else try stored token
        Resolve->>Store: get(base_url)
        Store-->>Resolve: stored_token?
        alt stored token found
            Resolve->>Server: GET /v1/auth/users/me (stored_token)
            alt 200 OK
                Server-->>Resolve: user_data
                Resolve-->>CLI: return stored_token
            else 401/403
                Resolve->>Store: delete(base_url)
                Resolve->>Server: POST /v1/auth/guest
                Server-->>Resolve: {access_token}
                Resolve-->>CLI: return access_token
            end
        else no stored token
            Resolve->>Server: POST /v1/auth/guest
            Server-->>Resolve: {access_token}
            Resolve-->>CLI: return access_token
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 Hopping through code with a twitch and a cheer,

Tokens lined up — explicit, stored, guest near.
User and password now step to the fore,
resolve_token hums and opens the door.
Tiny paws, tidy paths — a secure little chore.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feat/cli auth consistency' directly describes the main objective of the PR: refactoring and improving CLI authentication consistency across the codebase.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cli-auth-consistency
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Mar 19, 2026

Codecov Report

❌ Patch coverage is 82.79070% with 37 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.18%. Comparing base (366b01c) to head (020682a).
⚠️ Report is 2 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/zndraw/cli_agent/connection.py 50.00% 8 Missing ⚠️
src/zndraw/cli_agent/presets.py 12.50% 7 Missing ⚠️
src/zndraw/cli_agent/auth.py 44.44% 5 Missing ⚠️
src/zndraw/cli_agent/figures.py 25.00% 3 Missing ⚠️
src/zndraw/cli_agent/rooms.py 50.00% 3 Missing ⚠️
src/zndraw/cli_agent/screenshots.py 0.00% 3 Missing ⚠️
src/zndraw/cli_agent/sessions.py 0.00% 3 Missing ⚠️
src/zndraw/cli_agent/geometries.py 66.66% 2 Missing ⚠️
src/zndraw/cli_agent/frames.py 83.33% 1 Missing ⚠️
src/zndraw/cli_agent/jobs.py 50.00% 1 Missing ⚠️
... and 1 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #878      +/-   ##
==========================================
- Coverage   91.37%   91.18%   -0.19%     
==========================================
  Files         179      179              
  Lines       17292    17270      -22     
==========================================
- Hits        15800    15748      -52     
- Misses       1492     1522      +30     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
tests/test_client_integration.py (1)

105-156: ⚠️ Potential issue | 🟠 Major

Convert negative-case tests to module-level parametrized functions; inconsistent exception translation during auth.

The test structure violates the coding guideline that tests must be module-level functions with pytest.mark.parametrize, not class methods. The three negative auth cases (test_login_nonexistent_user_raises, test_login_wrong_password_raises, test_user_without_password_raises) should be combined into a single parametrized test.

Additionally, resolve_token() during ZnDraw.__post_init__() raises httpx.HTTPStatusError directly, whereas other API calls go through APIManager.raise_for_status() which translates to ZnDrawError. Consider whether auth failures should be wrapped consistently before escaping the public ZnDraw() API.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_client_integration.py` around lines 105 - 156, Combine the three
negative auth tests into one module-level pytest.mark.parametrize function
(replace test_login_nonexistent_user_raises, test_login_wrong_password_raises,
test_user_without_password_raises with a single parametrized test that supplies
(user,password,expected_exception) and asserts the expected exception when
calling ZnDraw(...)). Also make auth error translation consistent by changing
resolve_token() (called from ZnDraw.__post_init__) to not raise raw
httpx.HTTPStatusError: either call APIManager.raise_for_status() or catch
httpx.HTTPStatusError and re-raise ZnDrawError so auth failures are wrapped the
same way as other API calls (adjust the parametrized test to expect ZnDrawError
after this change).
src/zndraw/cli_agent/rooms.py (1)

61-80: ⚠️ Potential issue | 🟡 Minor

Add conn.close() to clean up the HTTP client.

The get_connection() call returns a Connection object wrapping an httpx.Client, which holds resources (connection pool, sockets). Both create_room (line 74) and set_default_room (line 161) create a connection but never call conn.close(). This causes resource leaks. Add cleanup:

with cli_error_handler():
    conn = get_connection(url, token, user, password)
    try:
        # ... make requests ...
    finally:
        conn.close()

Or refactor Connection to support the context manager protocol (__enter__/__exit__) for automatic cleanup.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zndraw/cli_agent/rooms.py` around lines 61 - 80, The create_room (and
similarly set_default_room) function opens a Connection via get_connection but
never closes it, leaking the underlying httpx.Client; update create_room to
ensure the Connection is cleaned up by wrapping the request logic so
conn.close() is always called (e.g., create a try/finally around the code that
uses conn and call conn.close() in finally), or make the Connection type support
context management and use it in a with statement; reference get_connection,
Connection, conn.close(), create_room and set_default_room when applying the
fix.
🧹 Nitpick comments (2)
src/zndraw/cli_agent/auth.py (1)

117-156: Keep auth status on the shared auth path.

status() now re-implements the validation and JWT-login branch instead of delegating to the shared resolver/helper, so auth-rule changes can drift here again. Consider factoring out a no-guest variant rather than carrying a second credential-resolution implementation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zndraw/cli_agent/auth.py` around lines 117 - 156, The status()
implementation duplicates credential validation and the JWT-login branch (the
token/user/password mutual-exclusion checks, the httpx POST to
"/v1/auth/jwt/login", and the store.get resolved-token fallback), which risks
drift from the shared resolver; refactor status() to call the existing shared
credential-resolution helper used elsewhere (or extract a small no-guest variant
in that shared module) instead of reimplementing logic, ensuring the helper
returns active_token and token_source (or equivalent) and that status() only
handles the json_print output path; retain the same semantics for
token/user/password checks, the login POST, and stored-token lookup when
delegating.
docs/superpowers/specs/2026-03-19-cli-auth-consistency-design.md (1)

112-118: Minor documentation inconsistency regarding backwards compatibility.

Lines 112-114 state "Hard rename, no backwards compatibility" for ZNDRAW_EMAILZNDRAW_USER, but Line 117 describes a migration warning when ZNDRAW_EMAIL is set. Consider clarifying: the rename is breaking (the old variable won't work), but there's a helpful warning to guide users during migration.

📝 Suggested clarification
 ## Environment Variables
 
-Hard rename, no backwards compatibility:
+Breaking rename with migration warning:
 
 - `ZNDRAW_EMAIL` -> `ZNDRAW_USER`
 - `ZNDRAW_PASSWORD` unchanged
 
-As a migration aid, `resolve_token()` emits a warning to stderr if `ZNDRAW_EMAIL` is set but `ZNDRAW_USER` is not, telling users to rename the variable.
+As a migration aid, `resolve_token()` emits a warning to stderr if `ZNDRAW_EMAIL` is set but `ZNDRAW_USER` is not. The old variable is not used; the warning tells users to rename it.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/superpowers/specs/2026-03-19-cli-auth-consistency-design.md` around
lines 112 - 118, Update the wording around the env var rename to clarify it's a
breaking change but that a migration aid exists: change "Hard rename, no
backwards compatibility" to explicitly state that ZNDRAW_EMAIL will no longer be
used (breaking) and that resolve_token() will still emit a stderr warning when
ZNDRAW_EMAIL is present but ZNDRAW_USER is not, guiding users to rename
ZNDRAW_EMAIL → ZNDRAW_USER; mention ZNDRAW_PASSWORD remains unchanged and that
the warning is only a temporary migration aid, not full backwards compatibility.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/zndraw/auth_utils.py`:
- Around line 60-76: The deprecation warning for ZNDRAW_EMAIL never populates
the legacy value into the user variable, causing a breaking change; before the
validation block that checks token/user/password, alias the old env var into the
expected name by setting user = user or os.environ.get("ZNDRAW_USER") or
os.environ.get("ZNDRAW_EMAIL") (preserving the existing warnings.warn call), so
that existing ZNDRAW_EMAIL+ZNDRAW_PASSWORD setups continue to work while still
emitting the DeprecationWarning; update the logic around the variables token,
user, and password in auth_utils.py accordingly.
- Around line 99-110: The code currently deletes the cached token whenever the
/v1/auth/users/me request returns non-200; change this to only evict on explicit
auth failures by checking resp.status_code and calling store.delete(base_url)
only when resp.status_code is 401 or 403. Locate the logic around
get_token_store(), the entry variable and the client call to "/v1/auth/users/me"
and modify the condition so that successful (200) returns the token, transient
errors (5xx/429/other) do not delete the entry, and only 401/403 trigger
store.delete(base_url).

In `@src/zndraw/cli_agent/connection.py`:
- Around line 229-241: The current except block for httpx.HTTPStatusError in the
_resolve_token() caller always treats errors as 401; update it to preserve the
upstream HTTP status by reading exc.response.status_code and pass that into die,
and set the exit code to EXIT_SERVER_ERROR for 5xx responses (use same logic as
cli_error_handler()) otherwise use EXIT_CLIENT_ERROR; keep the die message
"Authentication Failed" and include str(exc) for the detail. Reference:
_resolve_token, httpx.HTTPStatusError, die, EXIT_SERVER_ERROR,
EXIT_CLIENT_ERROR.

In `@tests/test_resolve_token.py`:
- Around line 42-71: Collapse the four similar tests that call resolve_token
into one pytest.mark.parametrize test (e.g., rename to
test_resolve_token_invalid_args) that iterates over tuples of (kwargs,
expected_message); for each case import resolve_token and call it inside with
pytest.raises(ValueError, match=expected_message). Parametrize the cases for:
({'token':'t','user':'u','password':'p'}, "Cannot combine"),
({'token':'t','password':'p'}, "Cannot combine"), ({'user':'u'}, "Missing
--password"), and ({'password':'p'}, "Missing --user") so the validation matrix
is concise and easy to extend.

---

Outside diff comments:
In `@src/zndraw/cli_agent/rooms.py`:
- Around line 61-80: The create_room (and similarly set_default_room) function
opens a Connection via get_connection but never closes it, leaking the
underlying httpx.Client; update create_room to ensure the Connection is cleaned
up by wrapping the request logic so conn.close() is always called (e.g., create
a try/finally around the code that uses conn and call conn.close() in finally),
or make the Connection type support context management and use it in a with
statement; reference get_connection, Connection, conn.close(), create_room and
set_default_room when applying the fix.

In `@tests/test_client_integration.py`:
- Around line 105-156: Combine the three negative auth tests into one
module-level pytest.mark.parametrize function (replace
test_login_nonexistent_user_raises, test_login_wrong_password_raises,
test_user_without_password_raises with a single parametrized test that supplies
(user,password,expected_exception) and asserts the expected exception when
calling ZnDraw(...)). Also make auth error translation consistent by changing
resolve_token() (called from ZnDraw.__post_init__) to not raise raw
httpx.HTTPStatusError: either call APIManager.raise_for_status() or catch
httpx.HTTPStatusError and re-raise ZnDrawError so auth failures are wrapped the
same way as other API calls (adjust the parametrized test to expect ZnDrawError
after this change).

---

Nitpick comments:
In `@docs/superpowers/specs/2026-03-19-cli-auth-consistency-design.md`:
- Around line 112-118: Update the wording around the env var rename to clarify
it's a breaking change but that a migration aid exists: change "Hard rename, no
backwards compatibility" to explicitly state that ZNDRAW_EMAIL will no longer be
used (breaking) and that resolve_token() will still emit a stderr warning when
ZNDRAW_EMAIL is present but ZNDRAW_USER is not, guiding users to rename
ZNDRAW_EMAIL → ZNDRAW_USER; mention ZNDRAW_PASSWORD remains unchanged and that
the warning is only a temporary migration aid, not full backwards compatibility.

In `@src/zndraw/cli_agent/auth.py`:
- Around line 117-156: The status() implementation duplicates credential
validation and the JWT-login branch (the token/user/password mutual-exclusion
checks, the httpx POST to "/v1/auth/jwt/login", and the store.get resolved-token
fallback), which risks drift from the shared resolver; refactor status() to call
the existing shared credential-resolution helper used elsewhere (or extract a
small no-guest variant in that shared module) instead of reimplementing logic,
ensuring the helper returns active_token and token_source (or equivalent) and
that status() only handles the json_print output path; retain the same semantics
for token/user/password checks, the login POST, and stored-token lookup when
delegating.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 10f0acdf-fec0-4b90-a6fd-35a9617166fb

📥 Commits

Reviewing files that changed from the base of the PR and between 366b01c and 1e8332b.

📒 Files selected for processing (29)
  • docker/production/docker-compose.yaml
  • docker/standalone/docker-compose.yaml
  • docs/superpowers/plans/2026-03-19-cli-auth-consistency.md
  • docs/superpowers/specs/2026-03-19-cli-auth-consistency-design.md
  • skills/zndraw/SKILL.md
  • src/zndraw/auth_utils.py
  • src/zndraw/cli_agent/admin.py
  • src/zndraw/cli_agent/auth.py
  • src/zndraw/cli_agent/bookmarks.py
  • src/zndraw/cli_agent/chat.py
  • src/zndraw/cli_agent/connection.py
  • src/zndraw/cli_agent/extensions.py
  • src/zndraw/cli_agent/figures.py
  • src/zndraw/cli_agent/frames.py
  • src/zndraw/cli_agent/geometries.py
  • src/zndraw/cli_agent/gif.py
  • src/zndraw/cli_agent/jobs.py
  • src/zndraw/cli_agent/mount.py
  • src/zndraw/cli_agent/presets.py
  • src/zndraw/cli_agent/rooms.py
  • src/zndraw/cli_agent/screenshots.py
  • src/zndraw/cli_agent/selection.py
  • src/zndraw/cli_agent/selection_groups.py
  • src/zndraw/cli_agent/sessions.py
  • src/zndraw/cli_agent/step.py
  • src/zndraw/client/core.py
  • tests/test_client_integration.py
  • tests/test_client_token_discovery.py
  • tests/test_resolve_token.py
💤 Files with no reviewable changes (1)
  • tests/test_client_token_discovery.py

Comment thread src/zndraw/auth_utils.py Outdated
Comment thread src/zndraw/auth_utils.py Outdated
Comment thread src/zndraw/cli_agent/connection.py
Comment thread tests/test_resolve_token.py Outdated
PythonFZ and others added 3 commits March 19, 2026 16:21
…ate_credentials

- Remove duplicate get_token_store() from connection.py, re-export from auth_utils
- Extract validate_credentials() helper in auth_utils, reuse in auth status
- Fix stale docstring in ZnDraw.password (no longer inferred from Settings)
- Remove unused _user/_password params from open_room command

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ion on 5xx

- Bridge ZNDRAW_EMAIL → user with DeprecationWarning (proper deprecation window
  instead of silent breakage for existing setups)
- Only evict stored token on 401/403, raise on transient 5xx/429
- Preserve real HTTP status code in CLI resolve_token error handler
- Parametrize validation tests into single test function

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/zndraw/cli_agent/connection.py (1)

224-236: ⚠️ Potential issue | 🟠 Major

Preserve the real failure class in this wrapper.

This still collapses upstream 5xx auth failures, transport errors, and malformed auth responses into synthetic 401s. That makes outages look like bad credentials, and the RequestError branch even prints a 401 while exiting with EXIT_CONNECTION_ERROR.

💡 Suggested mapping
     try:
         return _resolve_token(base_url, token=token, user=user, password=password)
     except ValueError as exc:
         die(str(exc), str(exc), 400, EXIT_CLIENT_ERROR)
     except httpx.HTTPStatusError as exc:
-        die("Authentication Failed", str(exc), 401, EXIT_CLIENT_ERROR)
-    except (httpx.RequestError, KeyError) as exc:
+        status = exc.response.status_code
+        title = "Authentication Failed" if status < 500 else "Server Error"
+        exit_code = EXIT_CLIENT_ERROR if status < 500 else EXIT_SERVER_ERROR
+        die(title, str(exc), status, exit_code)
+    except httpx.RequestError as exc:
         die(
-            "Authentication Failed",
-            f"Failed to authenticate: {exc}",
-            401,
+            "Connection Error",
+            str(exc),
+            503,
             EXIT_CONNECTION_ERROR,
         )
+    except KeyError as exc:
+        die("Server Error", f"Malformed auth response: {exc}", 502, EXIT_SERVER_ERROR)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zndraw/cli_agent/connection.py` around lines 224 - 236, The current
exception wrapper around _resolve_token collapses different failure classes into
synthetic 401s; update the except handlers in the function that calls
_resolve_token so they preserve the real failure class and status: keep the
ValueError -> die(..., 400, EXIT_CLIENT_ERROR) for malformed input, change the
httpx.HTTPStatusError handler to extract exc.response.status_code (or use 500 if
missing) and pass that status and EXIT_CLIENT_ERROR/EXIT_SERVER_ERROR
appropriately, change the httpx.RequestError handler to use a connection-related
status (e.g. 503) and EXIT_CONNECTION_ERROR and include the original exception
type/message, and handle KeyError separately (e.g. 502 or 500 with
EXIT_CONNECTION_ERROR) so logs and exit codes reflect actual upstream transport
vs auth vs response-parsing failures; reference _resolve_token,
httpx.HTTPStatusError, httpx.RequestError, KeyError, ValueError and die when
making changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/zndraw/cli_agent/auth.py`:
- Around line 113-119: The validate_credentials(token, user, password) call
inside the cli_error_handler block can raise ValueError which bubbles up as a
422; to keep this command consistent with other auth flows, catch ValueError
locally around validate_credentials in src/zndraw/cli_agent/auth.py and re-raise
or return an error that maps to the same 400 contract used by
connection.resolve_token(); i.e., wrap validate_credentials in a try/except
ValueError and convert that exception into the same CLI error/response used by
the resolve_token path (preserving the original message) so auth status behaves
consistently with the other auth-aware entrypoints.

In `@src/zndraw/cli_agent/connection.py`:
- Around line 24-25: The module removed the get_token_store symbol causing
ImportError in cli_agent/auth.py and cli_agent/admin.py; restore a re-export in
src/zndraw/cli_agent/connection.py by importing get_token_store from its
original module (where it was defined) and exposing it at module level (and add
it to __all__ if present) so existing imports like "from .connection import
get_token_store" continue to work; locate the symbol get_token_store and
re-export it from connection.py alongside the existing find_running_server
import.

---

Duplicate comments:
In `@src/zndraw/cli_agent/connection.py`:
- Around line 224-236: The current exception wrapper around _resolve_token
collapses different failure classes into synthetic 401s; update the except
handlers in the function that calls _resolve_token so they preserve the real
failure class and status: keep the ValueError -> die(..., 400,
EXIT_CLIENT_ERROR) for malformed input, change the httpx.HTTPStatusError handler
to extract exc.response.status_code (or use 500 if missing) and pass that status
and EXIT_CLIENT_ERROR/EXIT_SERVER_ERROR appropriately, change the
httpx.RequestError handler to use a connection-related status (e.g. 503) and
EXIT_CONNECTION_ERROR and include the original exception type/message, and
handle KeyError separately (e.g. 502 or 500 with EXIT_CONNECTION_ERROR) so logs
and exit codes reflect actual upstream transport vs auth vs response-parsing
failures; reference _resolve_token, httpx.HTTPStatusError, httpx.RequestError,
KeyError, ValueError and die when making changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3e54fa32-a036-4c96-8125-752104563fef

📥 Commits

Reviewing files that changed from the base of the PR and between 1e8332b and 826a19a.

📒 Files selected for processing (5)
  • src/zndraw/auth_utils.py
  • src/zndraw/cli_agent/auth.py
  • src/zndraw/cli_agent/connection.py
  • src/zndraw/cli_agent/rooms.py
  • src/zndraw/client/core.py
✅ Files skipped from review due to trivial changes (1)
  • src/zndraw/client/core.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/zndraw/auth_utils.py

Comment on lines 113 to +119
with cli_error_handler():
from zndraw.auth_utils import validate_credentials

resolved_url = resolve_url(url)
store = get_token_store()

validate_credentials(token, user, password)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Keep invalid credential combinations on the same 400 contract as the rest of the CLI.

validate_credentials() raises ValueError, and under cli_error_handler() that comes back as a 422. The shared connection.resolve_token() path returns 400 for the same bad --token / --user / --password combinations, so auth status is now the odd one out. Catch that ValueError locally if you want this command to stay aligned with the other auth-aware entrypoints.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zndraw/cli_agent/auth.py` around lines 113 - 119, The
validate_credentials(token, user, password) call inside the cli_error_handler
block can raise ValueError which bubbles up as a 422; to keep this command
consistent with other auth flows, catch ValueError locally around
validate_credentials in src/zndraw/cli_agent/auth.py and re-raise or return an
error that maps to the same 400 contract used by connection.resolve_token();
i.e., wrap validate_credentials in a try/except ValueError and convert that
exception into the same CLI error/response used by the resolve_token path
(preserving the original message) so auth status behaves consistently with the
other auth-aware entrypoints.

Comment thread src/zndraw/cli_agent/connection.py Outdated
Comment on lines 24 to 25
from zndraw.server_manager import find_running_server

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Re-export get_token_store here or the CLI will not import.

src/zndraw/cli_agent/auth.py and src/zndraw/cli_agent/admin.py still import get_token_store from .connection, and CI is already failing test collection with ImportError. Add the re-export back in this module, or update every remaining importer in the same PR.

🐛 Minimal fix
+from zndraw.auth_utils import get_token_store  # noqa: F401
 from zndraw.server_manager import find_running_server
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
from zndraw.server_manager import find_running_server
from zndraw.auth_utils import get_token_store # noqa: F401
from zndraw.server_manager import find_running_server
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zndraw/cli_agent/connection.py` around lines 24 - 25, The module removed
the get_token_store symbol causing ImportError in cli_agent/auth.py and
cli_agent/admin.py; restore a re-export in src/zndraw/cli_agent/connection.py by
importing get_token_store from its original module (where it was defined) and
exposing it at module level (and add it to __all__ if present) so existing
imports like "from .connection import get_token_store" continue to work; locate
the symbol get_token_store and re-export it from connection.py alongside the
existing find_running_server import.

PythonFZ and others added 2 commits March 19, 2026 16:34
The re-export from auth_utils was lost during rebase, causing ImportError
on CI for admin.py, auth.py, and any module importing get_token_store
from connection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
src/zndraw/cli_agent/connection.py (1)

24-24: ⚠️ Potential issue | 🔴 Critical

Critical: Missing get_token_store re-export causes CI import failures.

Both admin.py and auth.py import get_token_store from .connection, but connection.py does not define or re-export it (it is defined in auth_utils.py). This causes an ImportError at module load time. Add the re-export to fix.

🐛 Proposed fix
+from zndraw.auth_utils import get_token_store  # noqa: F401  # re-export for CLI modules
 from zndraw.server_manager import find_running_server
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zndraw/cli_agent/connection.py` at line 24, Add a re-export for
get_token_store in connection.py so modules importing it from .connection (e.g.,
admin.py and auth.py) succeed: import get_token_store from auth_utils (where it
is defined) and export it from connection.py (alongside the existing
find_running_server import) by adding a single import and ensuring
get_token_store is exposed from the module namespace.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/zndraw/cli_agent/connection.py`:
- Line 24: Add a re-export for get_token_store in connection.py so modules
importing it from .connection (e.g., admin.py and auth.py) succeed: import
get_token_store from auth_utils (where it is defined) and export it from
connection.py (alongside the existing find_running_server import) by adding a
single import and ensuring get_token_store is exposed from the module namespace.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9c476c0e-8c71-4bc4-b7b1-34b5bd01415d

📥 Commits

Reviewing files that changed from the base of the PR and between 826a19a and 9fe8857.

📒 Files selected for processing (3)
  • src/zndraw/auth_utils.py
  • src/zndraw/cli_agent/connection.py
  • tests/test_resolve_token.py

…n.py

Re-export via bare import was stripped by pre-commit as "unused".
Restore as a proper function definition that pre-commit won't touch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@PythonFZ PythonFZ merged commit fe40887 into main Mar 19, 2026
6 checks passed
@PythonFZ PythonFZ deleted the feat/cli-auth-consistency branch March 19, 2026 16:28
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.

2 participants