Skip to content

fix(auth): distinguish pending refresh during CLI startup - #913

Merged
mergify[bot] merged 3 commits into
matrixorigin:mainfrom
loveRhythm1990:fix/native-refresh-startup
Sep 29, 2026
Merged

mergify[bot] merged 3 commits into
matrixorigin:mainfrom
loveRhythm1990:fix/native-refresh-startup

Conversation

@loveRhythm1990

@loveRhythm1990 loveRhythm1990 commented Sep 29, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Starting Astra while a native credential refresh is pending currently treats the pending flag alone as proof that rotation was interrupted and tells the user to log in again. Identity-only callers also lose the refreshing credential provider or local resume metadata. This change lets those callers retain the generation-bound identity while token acquisition waits for the existing refresh owner and re-reads the settled state.

  • Resolve native startup authentication before cloud initialization can swallow its error. Show Refreshing your sign-in… after one second without cancelling or restarting the credential request; preserve the distinction between a busy refresh, connectivity failure, and reauthentication.
  • Make astra auth status observe the same rotation lock without refreshing tokens. Report refresh_in_progress for a held lock and reauthentication_required for pending intent observed after acquiring the lock.
  • Record private, bounded refresh diagnostics by default, including lock wait, transport/HTTP, signing-key verification, publication, and settlement stages. The 0600 auth-refresh.jsonl file is capped at 64 KiB; tokens and response bodies are excluded.

Related issue

Related to matrixorigin/matrixflow#17987. This PR fixes the client-side startup misclassification and diagnostic gap; it does not claim that every production re-login report had the same cause or close the broader issue.

Change type

  • Feature
  • Bug fix
  • Documentation
  • Refactor or performance improvement
  • Test
  • Build, CI, or maintenance

User and compatibility impact

Users with an active refresh wait for its existing owner instead of immediately receiving login advice. Normal fresh-token startup gains no artificial delay. Credential acquisition failures warn without preventing the interactive workbench from opening; native cloud initialization is skipped for that startup. A lock-wait timeout asks users to retry starting Astra. An abandoned or rejected rotation still requires login; an old refresh token is never replayed after an uncertain outcome.

astra auth status --json adds the refresh_in_progress state. Native credential schema, issuer policy, server/database configuration, refresh limits, and refresh-token replay rules are unchanged. Abrupt process exit can still interrupt settlement; OS lock release does not erase durable pending intent. Requires a new Astra client build.

Architecture and complexity delta

  • Canonical owner changed or extended: astra-credentials::native retains ownership of refresh locking, settlement, and local status; native_auth separates identity access from token access.
  • Existing implementations and callers searched: Binding::snapshot, profile identity binding, native credential projection, owner auth snapshots, interactive startup, credential helpers, and native auth status.
  • Superseded code, states, tables, shims, or self-only tests removed: inline lock acquisition is extracted for reuse by read-only status; duplicated identity validation is consolidated. No credential schema or table additions.
  • If parallel implementations remain, their boundary and retirement condition: none. Diagnostics are an observation file only and never authorize recovery. Legacy authentication follows its existing path.

Verification

  • cargo test -p astra-credentials --lib: 35 passed, including live rotation status, caller cancellation, real subprocess termination/lock release, account switch while waiting, no replay after rejected/lost responses, diagnostic permissions/size/redaction.
  • cargo test -p astra-cli --no-default-features --lib cli::native_auth::tests: 4 passed.
  • cargo test -p astra-cli --no-default-features --lib cli::session::session_startup::tests: 19 passed.
  • cargo test -p astra-cli --no-default-features --test native_auth_environment: 4 passed.
  • cargo clippy -p astra-credentials --all-targets -- -D warnings: passed.
  • cargo clippy -p astra-cli --no-default-features --lib --test native_auth_environment --no-deps -- -D warnings: passed.
  • cargo fmt --all -- --check and git diff --check: passed.
  • Public entrypoint exercised: isolated CLI login/environment tests and astra auth status --json against a pending refresh owned by another process. Startup identity, resume metadata, delayed progress, and settled-token acquisition are covered at their CLI owner boundary.
  • Unhappy paths exercised: live owner versus abandoned pending, account replacement, caller abort, process kill, rejected/invalid/lost responses, failed credential publication, and unsafe diagnostic paths. Tests use synthetic credentials and loopback services.
  • Database verification: not applicable; client-only change. No production mutations or live Keycloak/browser acceptance. Full workspace/online suites were not run because server and database behavior are unchanged. macOS debug linking emits an __eh_frame size warning; tests still complete successfully.

Final checklist

  • I added or updated tests at the layer that owns the behavior, or explained why no test is needed.
  • I updated public or design documentation for contract changes, or the change needs no documentation update.
  • I checked the diff for credentials, private URLs, customer data, generated files, and other sensitive information.
  • The PR title follows the repository's Conventional Commit format.

@XuPeng-SH XuPeng-SH left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed both commits through c2861fc, including the identity/token split, cross-process rotation locking, generation changes, cancellation/settlement, startup failure handling, read-only status, and diagnostic storage/redaction.

One P2 remains: the new continue-on-refresh-failure path reaches a banner that still interprets an unavailable token as being logged out. This misses an existing consumer of the distinction this PR introduces; details are inline.

Code-growth review: the patch is +1,031/-86, or +945 net. Separating inline test modules gives approximately +478 implementation lines, +433 test lines, and +34 documentation lines. The identity split and shared rotation-lock extraction have concrete purposes and retain the existing credential owner. The default bounded diagnostic sink also has a concrete purpose for intermittent incidents that cannot be investigated retroactively through opt-in CLI logging; it is not used as a second source of authentication state. The missing coverage is the failed token acquisition -> completed startup -> rendered authentication status path.

Non-blocking simplification: RotationDiagnostic::record contains no await points and only enqueues a detached blocking write. A synchronous record/enqueue method would remove the async wrapper and its call-site awaits, and make it clearer that returning does not mean the record has been written. This is a cleanup suggestion, not a separate correctness blocker.

Validation: the Test Suite and Static Checks workflows are successful for this exact head. I also inspected CI logs for the credential tests and CLI native-auth/startup unit tests. Local execution was unavailable because this review workspace has no Rust/Cargo toolchain; the banner finding is based on the concrete control/data flow, not a claimed end-to-end reproduction.

Comment thread crates/astra-cli/src/cli/session/session_startup.rs Outdated

@XuPeng-SH XuPeng-SH left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-reviewed the complete PR through 77c7b03 against base 62dc136, with particular attention to the change since my previous review at c2861fc. This approval supersedes my earlier request-changes decision for the startup-banner finding.

The P2 is addressed. startup_access_token now preserves AccessMiss instead of converting it to a display string. complete_session_startup retains the typed outcome, including any later acquisition failure, and passes its token-free projection to the banner. RefreshInProgress is rendered as "sign-in refreshing", reauthentication as "sign-in needs renewal", and unavailable/changed/signed-out states remain distinct. Bound account identity can supply the welcome text while an unsettled token stays withheld. Legacy authentication keeps its existing token-presence fallback. This extends the existing result flow without introducing a persistent UI authentication state machine.

Rechecked the generation-bound identity/provider path, refresh-lock waiting and reread, read-only status, cancellation/settlement, uncertain-response no-replay behavior, and diagnostic permissions, bounds and credential exclusion. No additional merge-blocking finding in this pass. Fresh credentials still avoid refresh-lock acquisition and rotation diagnostics.

The regression evidence is layered: the credential test holds the rotation lock past its test budget and checks RefreshInProgress; the native binding test preserves identity without exposing a pending token; the new isolated subprocess banner test checks both refreshing and reauthentication text, excludes "not logged in", and checks the account welcome. These cover the relevant owner boundaries. The new banner test captures subprocess output; it is not a complete 20-second startup/live-TUI reproduction, and should not be described as one.

Code-growth review: +1,123/-99, net +1,024: +490 in implementation files/sections (including comments), +497 tests, +37 documentation. The latest fix itself is +79 net: +12 implementation, +64 tests, +3 documentation. The typed propagation is small and justified. The shared credential owner and default bounded diagnostic sink retain the concrete purposes discussed in the first review. My earlier non-blocking simplification still applies: RotationDiagnostic::record has no await points, so making it a synchronous enqueue operation would remove unnecessary async/await plumbing.

Validation at submission: Static Checks passed for this exact head; the full Test Suite is still in progress. git diff --check passes and the review worktree is unchanged. Local Rust/Cargo execution and live issuer testing were unavailable. This is code-review approval; it does not claim the pending CI run has passed.

@mergify mergify Bot added the queued label Sep 29, 2026
@mergify

mergify Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Merge Queue Status

  • ✅ Entered queue — 2026-09-29 07:14 UTC · Rule: main · triggered by rule Automatic queue on approval for main
  • ✅ Checks skipped · PR is already up-to-date
  • ✅ Merged — 2026-09-29 07:14 UTC · at 53428af425098fa69c1cbce1f9e1b6010c2c9830 · squash

This pull request spent 11 seconds in the queue, including 1 second running CI.

Required conditions to merge
  • #review-threads-unresolved = 0 [🛡 GitHub branch protection]
  • github-review-approved [🛡 GitHub branch protection]
  • any of [🛡 GitHub branch protection]:
    • check-success = Check PR title
    • check-neutral = Check PR title
    • check-skipped = Check PR title
  • any of [🛡 GitHub branch protection]:
    • check-success = check
    • check-neutral = check
    • check-skipped = check
  • any of [🛡 GitHub branch protection]:
    • check-success = Test: core crates + bridge hooks
    • check-neutral = Test: core crates + bridge hooks
    • check-skipped = Test: core crates + bridge hooks
  • any of [🛡 GitHub branch protection]:
    • check-success = Test: astra-runtime
    • check-neutral = Test: astra-runtime
    • check-skipped = Test: astra-runtime
  • any of [🛡 GitHub branch protection]:
    • check-success = Test: astra-cli (edge-fs-tools)
    • check-neutral = Test: astra-cli (edge-fs-tools)
    • check-skipped = Test: astra-cli (edge-fs-tools)
  • any of [🛡 GitHub branch protection]:
    • check-success = Test: astra-cli (edge-git-gix)
    • check-neutral = Test: astra-cli (edge-git-gix)
    • check-skipped = Test: astra-cli (edge-git-gix)
  • any of [🛡 GitHub branch protection]:
    • check-success = Test: astra-cli (edge-rest)
    • check-neutral = Test: astra-cli (edge-rest)
    • check-skipped = Test: astra-cli (edge-rest)
  • any of [🛡 GitHub branch protection]:
    • check-success = Test: astra-cli (edge-shell)
    • check-neutral = Test: astra-cli (edge-shell)
    • check-skipped = Test: astra-cli (edge-shell)
  • any of [🛡 GitHub branch protection]:
    • check-success = Test: astra-cli (non-edge)
    • check-neutral = Test: astra-cli (non-edge)
    • check-skipped = Test: astra-cli (non-edge)
  • any of [🛡 GitHub branch protection]:
    • check-success = Test: online (core)
    • check-neutral = Test: online (core)
    • check-skipped = Test: online (core)
  • any of [🛡 GitHub branch protection]:
    • check-success = Test: online (integration)
    • check-neutral = Test: online (integration)
    • check-skipped = Test: online (integration)
  • any of [🛡 GitHub branch protection]:
    • check-success = Test: turn-core + services + plan
    • check-neutral = Test: turn-core + services + plan
    • check-skipped = Test: turn-core + services + plan
  • any of [🛡 GitHub branch protection]:
    • check-skipped = web (typecheck, test, build)
    • check-neutral = web (typecheck, test, build)
    • check-success = web (typecheck, test, build)
  • any of [🛡 GitHub branch protection]:
    • check-skipped = Astra SDK (typecheck, test+coverage, build)
    • check-neutral = Astra SDK (typecheck, test+coverage, build)
    • check-success = Astra SDK (typecheck, test+coverage, build)

@mergify
mergify Bot merged commit 53428af into matrixorigin:main Sep 29, 2026
35 of 36 checks passed
@mergify mergify Bot removed the queued label Sep 29, 2026
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