Skip to content

feat(cloud): add reflex cloud whoami and reflex cloud token - #6918

Open
Kastier1 wants to merge 5 commits into
mainfrom
feat/cloud-whoami-token
Open

feat(cloud): add reflex cloud whoami and reflex cloud token#6918
Kastier1 wants to merge 5 commits into
mainfrom
feat/cloud-whoami-token

Conversation

@Kastier1

@Kastier1 Kastier1 commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Why

Answering "which credentials is this machine actually using?" meant reading hosting_v1.json by hand. That came up chasing a customer whose token appeared not to update, and there was no supported way to confirm or disprove it.

What

reflex cloud whoami — resolves the token against the control plane and reports the identity, plus which source the token came from:

$ reflex cloud whoami
 field              value
 email              user@example.com
 user_id            …
 org_id             …
 tier               Pro
 is_service_account False
 token_source       config file
 token_fingerprint  sha256:714a6f6d0f454649
  • Never starts a browser login (unlike get_authenticated_client), so it is safe in CI and answers "am I logged in?" without changing the answer.
  • Never prints the token. token_fingerprint is a truncated sha256, so two machines can be compared in a support thread without anyone pasting a secret.
  • --json for scripting, --token to inspect a specific token.
  • On rejection it surfaces the auth request id for correlation with server-side logs.

reflex cloud token --print / --set TOKEN / --clear — exactly one required.

  • --print writes the raw token to stdout via click.echo, deliberately bypassing console.print, which wraps at 80 columns when piped and would corrupt a long token in export REFLEX_ACCESS_TOKEN=$(reflex cloud token --print). The log level is forced to ERROR for this path, because the shared console writes everything below ERROR to stdout; stdout now carries the token or nothing.
  • --set validates before saving, then reads back to confirm the write — save_token_to_config swallows write errors, so success was previously unverifiable. A rejected token exits non-zero and leaves the existing one untouched.
  • --clear removes the stored token and confirms removal, distinguishing three outcomes: gone (success), still present (exit 1), config unreadable (exit 1). It notes when REFLEX_ACCESS_TOKEN is still set and will now take over.

get_existing_access_token_with_source exposes the existing precedence (config file wins; REFLEX_ACCESS_TOKEN is only a fallback) so both commands can report which source won. get_existing_access_token delegates to it — no behavior change. stored_access_token reads the config directly, ignoring the environment and propagating read errors, so callers can tell "no token stored" from "cannot tell what is stored".

Bug found in review: config writes were destructive

save_token_to_config and delete_token_from_config open the config with mode "w", which truncates on open, before json.dump runs. Any write failure therefore destroyed the stored credentials — and since neither helper reports failure to the caller, it surfaced only as a log line. Reproduced by patching json.dump to raise:

before: {"access_token": "GOOD-TOKEN", "project": "p1"}
after:  (empty file)

Both now serialize to a temporary file alongside the target and move it into place with Path.replace, closing the handle first so Windows can rename it and cleaning up the temp file on failure. A failed write leaves the previous credentials byte-identical. This also fixes reflex login and reflex logout, which share these helpers.

A follow-up review round caught a regression in that first fix: routing reads through a helper that returned {} on failure turned "cannot read" into "is empty", so delete_token_from_config replaced a malformed config with {}. Verified against a main worktree — main left the file untouched. Reads now propagate errors, delete leaves an unreadable config alone, and save keeps its fallback of starting fresh so a corrupt config cannot block re-authenticating.

Incidental fix: tests were destroying the developer's login

Not cosmetic, and the reason this PR touches conftest.py:

  • test_save_token_to_config mocks Path.exists and Path.mkdir but not Path.open, so it overwrote the real hosting_v1.json with {"access_token": "test_token"}.
  • test_authenticated_token_found_but_invalid calls the real delete_token_from_config, emptying it to {}.

Verified with a sentinel: before, pytest tests/units/reflex_cli reduced the file to {}; after, it is untouched. An autouse fixture now points Reflex.DIR and both HOSTING_JSON paths at a tmp dir, so no reflex_cli test can reach the real file.

With that isolation in place, the config-file tests were rewritten to assert real on-disk contents instead of mock call counts. The old assertions (mocked_open.call_count == 2) pinned the exact non-atomic write sequence, so they would have blocked the fix above while proving nothing about the resulting file.

Testing

  • 20 tests in tests/units/reflex_cli/v2/test_auth.py; reflex_cli/v2/auth.py at 100% coverage. Includes: a bad --set neither saves nor deletes, a failed write preserves the previous token and leaks no temp file, an unreadable config is preserved rather than replaced, --print round-trips a 300-char token verbatim and stays clean under --loglevel debug, --clear distinguishes all three removal outcomes, whoami never opens a browser, and the token never appears in either output mode.
  • The --print stdout test exercises the real lookup rather than mocking it, because the contaminating debug records originate inside the helper. Confirmed it fails when the fix is reverted.
  • Config-file and token-precedence tests reworked against the real (isolated) filesystem.
  • Full suite: 7639 passed, coverage 74.80%. ruff check/format clean. pyright reflex tests unchanged at 5 pre-existing errors (recharts/lucide stubs, unrelated).
  • Smoke-tested against prod: whoami, --print under --loglevel debug, --clear, and the usage error.

Docs need no change — docs/.../cloud_cliref.py generates the CLI reference from the click tree, so both commands appear automatically. Command docstrings carry no Args:/Raises: sections, matching every other command in the package, because a click docstring is its --help text.

Deliberate choices worth a reviewer's attention

  1. Both commands call hosting.validate_token directly rather than validate_token_with_retries, which deletes the cached token on access-denied — a bad side effect for --set in particular. The trade-off is no retry on a transient failure.
  2. --set has no offline escape hatch; if validation cannot reach the control plane, nothing is saved. Strict on purpose, since silently storing an unusable token is the failure mode this PR exists to expose. --token and REFLEX_ACCESS_TOKEN remain available meanwhile.
  3. --print suppresses diagnostics rather than routing them to stderr. The stdout-for-non-errors split is a global convention of the shared console; rerouting it belongs in its own change. reflex cloud whoami --loglevel debug gives the lookup diagnostics.
  4. save and delete deliberately disagree about an unreadable config: save starts fresh so login always works, delete refuses to touch what it cannot read. Both are tested as policies.

Follow-ups, not in this PR

  • A rejected token reports as server error, not access denied: validate_token maps a 401 (raise_for_statusHTTPStatusErrorhttpx.HTTPError) to TokenValidationError, and only a JSON parse failure reaches TokenAccessDeniedError. Since validate_token_with_retries only clears the cached token on ValueError, a revoked token is never evicted from hosting_v1.json — and keeps shadowing a valid REFLEX_ACCESS_TOKEN indefinitely.
  • reflex-hosting-cli ignores REFLEX_DIR, which relocates the framework's data dir, so the token file diverges from the rest of reflex state. Fixing it moves an existing file and needs a read-fallback migration.

🤖 Generated with Claude Code

Inspecting which Reflex Cloud credentials a machine is actually using
required reading `hosting_v1.json` by hand. Two commands make it
first-class:

- `reflex cloud whoami` resolves the token with the control plane and
  reports the account, org, tier and where the token was loaded from.
  It never starts a browser login and never prints the token.
- `reflex cloud token --print/--set/--clear` reads, replaces or removes
  the stored token. `--set` validates before saving, so a bad token
  cannot silently replace a working one.

`get_existing_access_token_with_source` exposes the existing config-file
over-`REFLEX_ACCESS_TOKEN` precedence so both commands can report which
source won, with `get_existing_access_token` delegating to it.

Also isolates the hosting config in tests: `test_save_token_to_config`
and `test_authenticated_token_found_but_invalid` wrote to and emptied the
developer's real token file, logging them out on every test run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Kastier1
Kastier1 requested a review from a team as a code owner August 20, 2026 17:12
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@greptile-apps

greptile-apps Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds commands for inspecting the active Reflex Cloud identity and managing stored access tokens, while hardening credential-file writes and isolating CLI tests from developers’ real credentials.

  • Adds reflex cloud whoami with identity, token-source, fingerprint, table, and JSON output.
  • Adds validated token print, set, and clear operations.
  • Makes token configuration updates atomic and preserves unreadable configurations on deletion.
  • Adds filesystem-backed regression coverage and isolated test configuration paths.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the previously reported destructive write, false clear success, and legacy cleanup issues are addressed at the current head.

Important Files Changed

Filename Overview
packages/reflex-hosting-cli/src/reflex_cli/utils/hosting.py Adds token-source reporting, direct stored-token inspection, and atomic configuration writes with failure cleanup.
packages/reflex-hosting-cli/src/reflex_cli/v2/auth.py Implements the new identity inspection and token-management commands with validation and post-operation verification.
packages/reflex-hosting-cli/src/reflex_cli/v2/deployments.py Registers the new whoami and token commands under reflex cloud.
tests/units/reflex_cli/conftest.py Redirects hosting configuration paths to temporary directories for CLI unit tests.
tests/units/reflex_cli/utils/test_hosting.py Adds real-filesystem coverage for token precedence, atomic-write failures, malformed configuration, and legacy cleanup.
tests/units/reflex_cli/v2/test_auth.py Comprehensively covers command output, validation, persistence verification, clear outcomes, and token secrecy.

Reviews (4): Last reviewed commit: "fix(cloud): keep `token --clear` failure..." | Re-trigger Greptile

Comment thread packages/reflex-hosting-cli/src/reflex_cli/v2/auth.py
Comment thread packages/reflex-hosting-cli/src/reflex_cli/v2/auth.py
Comment thread packages/reflex-hosting-cli/src/reflex_cli/v2/auth.py
@codspeed-hq

codspeed-hq Bot commented Aug 20, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 27 untouched benchmarks
⏩ 8 skipped benchmarks1


Comparing feat/cloud-whoami-token (4c3b595) with main (d86f167)

Open in CodSpeed

Footnotes

  1. 8 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 6 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/reflex-hosting-cli/src/reflex_cli/v2/auth.py Outdated
Comment thread packages/reflex-hosting-cli/src/reflex_cli/v2/auth.py
Comment thread packages/reflex-hosting-cli/src/reflex_cli/v2/auth.py
Kastier1 and others added 2 commits August 20, 2026 14:46
Review found `save_token_to_config` and `delete_token_from_config` open
the config with mode "w", which truncates before the write is attempted.
A failing write (disk full, I/O error) therefore destroyed the existing
token and project, and both helpers swallow the exception, so the caller
saw only a warning. `token --set` read back and reported the failure, but
only after the damage was done.

Both now serialize to a temporary file alongside the target and move it
into place, so a failed write leaves the previous credentials untouched
and the temporary file is cleaned up.

`token --clear` now reads back too: `delete_token_from_config` swallows
filesystem errors, so success was previously reported without evidence.

`token --print` forces the log level to ERROR while resolving. The shared
console writes everything below ERROR to stdout, so
`$(reflex cloud token --print --loglevel debug)` captured two Debug lines
along with the token. Errors go to stderr, so stdout now carries the
token or nothing.

The config-file tests were rewritten against the real (now isolated)
filesystem instead of asserting mock call counts, which pinned the old
non-atomic write sequence, and cover the failed-write paths.

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

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 6 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/reflex-hosting-cli/src/reflex_cli/v2/auth.py Outdated
Comment thread tests/units/reflex_cli/v2/test_auth.py Outdated
Comment thread packages/reflex-hosting-cli/src/reflex_cli/utils/hosting.py
Comment thread packages/reflex-hosting-cli/news/6918.bugfix.md Outdated
Comment thread packages/reflex-hosting-cli/src/reflex_cli/v2/auth.py Outdated
…g it

Follow-up review caught a regression from the atomic-write change:
`_read_hosting_config` swallowed read errors and returned `{}`, so
`delete_token_from_config` replaced a malformed config with `{}`,
destroying the token and project it could not parse. On main the read and
the write shared one try block, so a parse failure aborted before the
write. Confirmed against a main worktree: main leaves the malformed file
untouched, this branch emptied it.

`_read_hosting_config` now returns `{}` only for a missing file and
propagates read and parse errors. `delete_token_from_config` lets those
reach its existing handler, so an unreadable config is left alone.
`save_token_to_config` keeps its previous fallback of starting from an
empty config, so a corrupt file cannot block re-authenticating.

`token --clear` verified removal through a lookup that treats an
unreadable config as "no token", so it could report success while the
token was still on disk. It now reads the config directly through
`stored_access_token`, which distinguishes absent from unparseable, and
fails on either kind of unconfirmed removal.

The `--print` stdout test mocked the very helper whose debug records
contaminate stdout, so it passed with or without the fix. It now writes a
real config and exercises the lookup; verified it fails without the fix.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread packages/reflex-hosting-cli/src/reflex_cli/v2/auth.py

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 5 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/reflex-hosting-cli/src/reflex_cli/utils/hosting.py
Two paths could escape the command's error handling as tracebacks.

`delete_token_from_config` removed the legacy `hosting_v0.json` outside
its try block, so an unlink failure propagated out and aborted `--clear`
before the readback ran. The cleanup is now best-effort like the rest of
the function, and uses `missing_ok=True` to close the exists/unlink race.
The legacy file holds no token the CLI reads, so failing to remove it
must not fail the removal that already succeeded.

`stored_access_token` indexed whatever `json.load` returned, so a config
holding valid JSON that is not an object raised `AttributeError` — not
one of the types `--clear` catches. `_read_hosting_config` now rejects
non-object JSON as a `ValueError`, which every caller already handles,
making its dict return type honest.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

1 participant