Skip to content

feat: multi-profile authentication#275

Open
Zious11 wants to merge 44 commits intodevelopfrom
feat/multi-profile-auth
Open

feat: multi-profile authentication#275
Zious11 wants to merge 44 commits intodevelopfrom
feat/multi-profile-auth

Conversation

@Zious11
Copy link
Copy Markdown
Owner

@Zious11 Zious11 commented Apr 25, 2026

Summary

Lets jr target multiple Atlassian Cloud sites from one install, with jr auth switch <profile> to flip between them. Shared classic API token across profiles (account-level credential authenticates the same user against any Atlassian site), per-profile OAuth tokens (cloudId-scoped, not transferable). Auto-migration of legacy single-instance configs; lazy keyring migration for OAuth tokens.

Highlights:

  • New CLI surface: jr auth login --profile <N> --url <U>, jr auth switch <N>, jr auth list, jr auth logout [--profile N], jr auth remove <N>, jr auth status [--profile N], jr auth refresh [--profile N]. New global --profile <N> flag.
  • Config schema: [profiles.<name>] table-of-tables + default_profile field. Legacy [instance]/[fields] blocks read for migration only and no longer serialized.
  • Keyring layout: shared email/api-token/oauth_client_* keys + per-profile <profile>:oauth-access-token/<profile>:oauth-refresh-token. Lazy migration of pre-existing flat OAuth keys to default:* on first read.
  • Cache layout: ~/.cache/jr/v1/<profile>/ per-profile subdirectory; legacy flat files orphan via path versioning (no migration code, matches pip/cargo/npm convention).
  • Profile name validation: [A-Za-z0-9_-]{1,64}, plus rejects Windows reserved names (CON, NUL, AUX, PRN, COM1-9, LPT1-9) cross-platform for portable configs.

Backward Compatibility

For typical CLI users: effectively non-breaking. Existing commands (jr auth login, auth status, auth refresh, all flags) continue to work unchanged. The first run after upgrading auto-migrates the config (one stderr notice, idempotent). API tokens are preserved as-is; OAuth tokens lazy-migrate on first read.

Structurally breaking for direct consumers of jr's internal state:

  • ~/.config/jr/config.toml shape changes from [instance] to [profiles.default] after first migration
  • OS keychain entry names change from oauth-access-token to default:oauth-access-token after lazy migration
  • Cache directory layout changes from ~/.cache/jr/*.json to ~/.cache/jr/v1/<profile>/*.json (old files orphan harmlessly)

Squash-merge guidance: add a BREAKING CHANGE: footer to the merge commit so the change is captured in conventional-commits-aware tooling and changelog generation:

BREAKING CHANGE: config.toml schema, OS keychain key names, and cache
directory layout have all changed shape. Existing users are migrated
automatically on first `jr` invocation after upgrading; scripts that
parse jr's internal state directly (config TOML, keychain entries,
cache files) need updating to the new shapes.

We're at 0.5.0-dev.3 (pre-1.0), so semver allows the change in a 0.6.0 minor bump.

Implementation

Spec: docs/specs/multi-profile-auth.md
Plan: docs/superpowers/plans/2026-04-24-multi-profile-auth.md

16 TDD tasks landed as 18 commits (foundation → keyring → cache → client → CLI surface → migration → cleanup → docs). Each commit is green and bisectable. Dual-shape transition kept legacy fields readable until Task 16 stopped serializing them.

Design decisions validated via Perplexity at every step: kubectl-style shared users + per-host caches, gh-style consolidated auth subcommand surface, pip/cargo-style versioned cache root for migration, AWS-style per-profile field duplication, lazy/opportunistic keyring migration vs upfront TOML migration.

Test Plan

  • cargo fmt --all -- --check passes
  • cargo clippy --all-targets -- -D warnings passes
  • cargo test — 957 tests pass (was 918 before this branch; 39 new)
  • JR_RUN_KEYRING_TESTS=1 cargo test --lib api::auth -- --ignored opt-in keyring round-trip + lazy migration tests gated for CI portability
  • Spec + plan committed before implementation
  • Each task implemented TDD (red → green → commit)
  • README + CLAUDE.md updated to reflect new CLI surface, config schema, cache path, and agent-relevant env vars

Out of Scope (tracked as follow-ups)

  • jr profile subcommand tree (consolidated under jr auth per gh precedent)
  • Profile renaming (login + remove workaround)
  • Per-repo .jr.toml profile pinning (use direnv with JR_PROFILE)
  • KeyringProvider trait abstraction for testability/CI portability
  • Atomic Config::save_global (tempfile + rename)
  • Bulk-clear command for shared credentials
  • tests/migration_legacy.rs mutates process-global XDG_CONFIG_HOME; passes individually but flakes under parallel test execution. Worth either gating with serial_test or scoping the env-var contract more tightly in a follow-up.

Zious11 added 20 commits April 24, 2026 22:42
Lets jr target multiple Atlassian Cloud sites (prod, sandbox, etc.)
from one install, with `jr auth switch <profile>` to flip between
them. Shares the classic API token across profiles (account-level
credential authenticates the same user against any site) while
keeping OAuth tokens per-profile (cloudId-scoped, not transferable).

Design decisions validated against industry conventions via Perplexity:
kubectl-style shared users + per-host caches, gh-style consolidated
auth subcommand surface, pip/cargo-style versioned cache root for
schema migration, AWS-style per-profile field duplication, lazy/
opportunistic keyring migration vs upfront TOML migration.

Spec: docs/specs/multi-profile-auth.md
Perplexity review surfaced one real gap and three pre-existing
limitations worth surfacing:

- Profile names like CON, NUL, AUX, COM1-9, LPT1-9 pass the
  character regex but fail at OS level on Windows. Reject them
  explicitly on every platform so configs stay portable.
- Concurrent jr invocations writing config.toml can race
  (last-writer-wins). Pre-existing; tracked as atomic-save
  follow-up.
- Concurrent OAuth refresh against the same profile can race the
  same way. Pre-existing; the existing 401 retry path masks it.
- Cross-machine portability: TOML config is portable, keyring
  secrets are not (by design). Document that re-login on a new
  machine is the expected flow.
16 TDD tasks plus final verification, derived from the spec at
docs/specs/multi-profile-auth.md. Tasks ordered foundation-first
(validation → types → resolution → migration) so each commit stays
green: dual-shape GlobalConfig keeps legacy [instance] readable
during transition, refactored call sites land per-file, cleanup of
legacy serde fields ships last.

Each task contains exact file paths, complete code blocks, exact
commands, and TDD red-green-refactor steps sized for 2-5 minute
increments.
Introduces ProfileConfig and the dual-shape GlobalConfig (default_profile +
profiles map) as foundation for multi-profile auth. Legacy
InstanceConfig/FieldsConfig remain in place to be read for migration in a
later task.

Update init.rs to spread GlobalConfig::default() so the new fields are
populated when constructing the struct literal.
Add `resolve_active_profile_name` free function applying the precedence
chain (--profile flag > JR_PROFILE env > default_profile config field >
literal "default") and a new `Config::active_profile_name` field
populated by `Config::load`. Add `Config::active_profile()` (lookup with
empty default) and `Config::active_profile_or_err()` (strict variant
listing known profiles in the error message).

Update existing `Config { .. }` literal constructors in tests and
`cli::init` to set `active_profile_name: String::new()`. The CLI flag
override is read from the `JR_PROFILE_OVERRIDE` env var, which Task 9
will populate from the parsed `--profile` flag in main.
…ault]

Adds `migrate_legacy_global` (pure copy from legacy `[instance]`+`[fields]`
into a new `[profiles.default]`) and wires `Config::load` to run it once,
persist via the new `save_global_to` helper, and emit a one-time stderr
notice. Legacy fields are intentionally PRESERVED in the migrated shape so
callers still reading `global.instance.*` / `global.fields.*` keep working
until Tasks 7/8 migrate them to `active_profile()`. Task 16 will stop
serializing the legacy fields, dropping them from disk on next save.

Adds `Clone` to `GlobalConfig`, `InstanceConfig`, `FieldsConfig`,
`DefaultsConfig` for the migration test fixtures and any future
copy-on-modify shape.

Refactors `Config::save_global` to delegate to `save_global_to`.
Cache is now keyed by profile name to support multi-profile auth. All
public readers/writers take `profile: &str` as their first argument and
store under `~/.cache/jr/v1/<profile>/`. Adds `clear_profile_cache` for
profile-scoped removal.

Production call sites pass `&config.active_profile_name` where Config is
in scope, or the literal "default" as a stopgap until Task 7 threads the
active profile through JiraClient. The integration-test team-cache
fixture is updated to write under the new v1/<profile> layout.
When `jr init` runs against a config with existing profiles, prompt to
add another rather than overwriting. New profile name is validated and
exported via JR_PROFILE_OVERRIDE so the rest of init scopes its writes
to the new profile.

Adds:
- tests/auth_profiles.rs — exit codes, fresh install, precedence chain
  (--profile > JR_PROFILE > default_profile)
- tests/migration_legacy.rs — legacy [instance] -> [profiles.default]
  migration is correct and idempotent
Updates the user-facing README:
- Commands table: add jr auth switch / list / logout / remove; document
  --profile and --url on jr auth login
- Global flags table: document --profile precedence chain
- Configuration section: replace [instance] example with new
  [profiles.default] + [profiles.sandbox] shape; document switching
  options (auth switch / --profile / JR_PROFILE); note migration of
  legacy configs is automatic
- Cache path: ~/.cache/jr/v1/<profile>/teams.json (was flat)
- Scripting note: --profile + JR_PROFILE for agent workflows

Updates CLAUDE.md (developer-facing):
- Architecture tree: --profile global flag, expanded auth.rs purpose,
  per-profile keychain layout, versioned per-profile cache root
- Gotchas: multi-profile boundary rule (every cache call takes profile),
  per-profile vs shared OAuth keys, cache-root versioning escape hatch
- AI Agent Notes: JR_PROFILE env var, --profile flag precedence,
  JR_RUN_KEYRING_TESTS=1 gate for opt-in keyring round-trip tests
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds multi-profile authentication to jr, enabling multiple Atlassian Cloud sites per install with profile-aware config, keyring entries, and caches.

Changes:

  • Introduces new config schema (default_profile + [profiles.<name>]) with legacy [instance]/[fields] migration.
  • Namespaces OAuth tokens and caches per profile (<profile>:oauth-* in keyring, ~/.cache/jr/v1/<profile>/ on disk).
  • Expands jr auth CLI surface (switch/list/logout/remove/status/refresh) and adds a global --profile flag.

Reviewed changes

Copilot reviewed 28 out of 28 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
src/config.rs Adds profile schema/types, migration, active-profile resolution, and validation helpers.
src/api/auth.rs Namespaces OAuth tokens by profile; adds clear helpers and lazy legacy-key migration.
src/cache.rs Moves caches to per-profile v1/<profile> directories; updates cache APIs.
src/api/client.rs JiraClient now consumes the active profile for URL/auth decisions.
src/cli/mod.rs Adds global --profile and expands auth subcommands/flags.
src/main.rs Wires --profile into config loading via JR_PROFILE_OVERRIDE.
src/cli/auth.rs Implements multi-profile auth workflows (login/list/switch/logout/remove/status/refresh).
src/cli/init.rs Attempts to make jr init multi-profile aware and update cache warming.
src/cli/team.rs Threads active profile into team cache and org-id persistence.
src/cli/issue/* + src/api/assets/* + src/api/jsm/servicedesks.rs Updates some cache calls, but several still hard-code "default".
tests/auth_profiles.rs New integration tests for profile workflows and precedence.
tests/migration_legacy.rs New integration tests for legacy config migration.
docs/specs/multi-profile-auth.md / docs/superpowers/plans/... Spec + implementation plan for the feature.
src/output.rs Adds print_warning() helper.
src/cli/snapshots/*.snap Snapshot for jr auth list table rendering.
Comments suppressed due to low confidence (1)

src/cli/init.rs:87

  • When jr init is adding a new profile, it sets JR_PROFILE_OVERRIDE but still calls login_oauth("default", ...) / login_token("default", ...). Since the login flows now namespace OAuth tokens and persist auth_method by the passed profile name, this will authenticate/update the wrong profile. Use the resolved active/override profile name (or thread profile_name through) instead of the hard-coded "default".
    if auth_choice == 0 {
        crate::cli::auth::login_oauth("default", None, None, false).await?;
    } else {
        crate::cli::auth::login_token("default", None, None, false).await?;
        let mut config = Config::load()?;
        config.global.instance.auth_method = Some("api_token".into());
        config.save_global()?;
    }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/api/assets/linked.rs
Comment thread src/api/assets/objects.rs
Comment thread tests/migration_legacy.rs Outdated
Comment thread docs/specs/multi-profile-auth.md Outdated
Comment thread src/cli/init.rs Outdated
Comment thread src/cli/issue/workflow.rs Outdated
Comment thread src/api/jsm/servicedesks.rs
Comment thread src/api/assets/workspace.rs
Comment thread src/config.rs Outdated
Comment thread src/config.rs Outdated
Zious11 added 2 commits April 25, 2026 09:13
- Plumb active profile name through JiraClient (5 sites had hard-coded
  "default" stopgaps; cache isolation now correct for non-default profiles)
- jr init: persist URL/cloud_id/org_id/auth_method into the active
  profile entry instead of legacy [instance] (which is now skip_serializing
  and would silently drop the writes)
- Config::load migration trigger: check all 7 legacy fields, not just
  url + team_field_id, so configs with only cloud_id/auth_method/etc.
  also migrate
- Config::load: validate the resolved active_profile_name against the
  regex so a malicious JR_PROFILE=foo:bar can't flow into cache paths
  or keyring keys
- tests/migration_legacy.rs: serialize the two tests with a Mutex to
  avoid process-global XDG_CONFIG_HOME race under parallel execution
- docs/specs: refresh is not OAuth-only; document both auth-method paths
Both helpers were still reading config.global.instance.{auth_method,
oauth_scopes}. Since Task 16 made [instance] skip_serializing, any
config saved post-migration would have those fields back at None on
the next load — breaking the user's auth method preference and any
custom OAuth scope override. Switch to active_profile() reads.

Test fixtures (config_with_auth_method, config_with_oauth_scopes)
updated to construct configs via the [profiles.default] shape.

Caught during PR #275 review by the fix subagent while addressing
Copilot's findings — flagged as out-of-scope for that batch and
fixed here in a focused follow-up.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 28 out of 28 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/cli/auth.rs
Comment thread src/main.rs Outdated
Comment thread src/cli/init.rs Outdated
Comment thread tests/migration_legacy.rs Outdated
Comment thread tests/auth_profiles.rs
Comment thread src/cli/auth.rs
Comment thread src/cli/auth.rs Outdated
Critical:
- jr auth refresh --profile X: chosen_flow now inspects the target
  profile's auth_method, not the active profile's. Refreshing a
  non-active profile no longer dispatches the wrong flow. Factored
  out chosen_flow_for_profile(&ProfileConfig, bool) and have
  refresh_credentials thread the resolved target profile in.
- jr auth refresh --profile X: only the target's credentials are
  cleared. OAuth profiles use clear_profile_creds (per-profile keys
  only); api_token profiles still use clear_all_credentials because
  the API token IS the shared credential being refreshed. Refreshing
  an OAuth profile no longer wipes the shared API token used by other
  profiles.

Important:
- jr auth logout --profile X: errors with exit 64 when X doesn't
  exist (was silently succeeding via NoEntry-treated-as-success).
  Consistent with switch/remove.
- unsafe { std::env::set_var(...) } SAFETY comments updated in
  main.rs, cli/init.rs, and tests/migration_legacy.rs to describe
  the actual invariant (pre-runtime / serial-flow / mutex-guarded)
  rather than the inaccurate "single-threaded" claim.

Minor:
- tests/auth_profiles.rs precedence test asserts exactly one active
  profile before indexing, so a malformed JSON shape gives a clear
  failure message.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 28 out of 28 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/cli/auth.rs
Comment thread src/cli/auth.rs
…oauth

Round-15 Copilot finding: login_token and login_oauth persisted the
target profile's auth_method but never touched global.default_profile.
handle_login goes through prepare_login_target (which promotes the
target if default_profile is None), but refresh_credentials calls
login_token/login_oauth directly. Result: `jr auth refresh --profile
sandbox` on a fresh install (no profiles, no default_profile) created
the sandbox profile but left default_profile = None. The next strict
Config::load() then errored trying to resolve the literal "default"
against a profiles map that only had sandbox.

Apply the same default_profile fallback inside login_token and
login_oauth so any caller (handle_login, refresh_credentials, future
direct callers) leaves config in a coherent state.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 28 out of 28 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/cli/auth.rs
Comment thread docs/specs/multi-profile-auth.md Outdated
- refresh_credentials now refuses early when flow == Token AND the
  target profile has no URL configured. Previously, refresh on a
  fresh install / hand-edited URL-less profile would re-prompt for
  email/token via the api_token flow and report success — leaving
  the profile unusable for any actual API call. The new error tells
  the user to run \`jr auth login --profile X --url <https://...>\`
  instead, citing the actual root cause. OAuth flow keeps writing
  its own URL via accessible-resources, so it doesn't have this gap.
- Update auth_refresh_no_input_fails_with_clear_message to assert
  the new error wording (no URL configured + jr auth login --url
  recovery hint) instead of the pre-fix email-required wording.
- Spec: drop the "Confirmation prompt unless --no-input" claim about
  --url overwrites on existing profiles. \`prepare_login_target\` has
  always overwritten unconditionally; passing --url is itself the
  user's explicit confirmation of intent. Adding a prompt mid-flow
  would hurt the agent-friendly invariant. Spec now reflects the
  actual code semantics.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 29 out of 29 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/config.rs
Round-17 Copilot finding: Config::load ran the migration detection
against an env-merged Figment view AND persisted the migrated value
to disk via save_global_to. Any transient JR_* env vars set for the
upgrade invocation (e.g., JR_DEFAULTS_OUTPUT=json) were silently
baked into the saved config.toml, persisting across future
invocations even after the env var was unset.

Fix: when migration is needed, perform a SECOND file-only Figment
load (no Env layer) to drive the save. The env-merged in-memory
`global` still gets migrated so callers see the new
[profiles.default] entry, but on-disk state reflects only what was
in the file plus the structural migration — never transient env
values.

The narrower "two-phase load (file-only first, save, then env-
merged)" approach Copilot suggested was tried and reverted because
it broke 10 team-related tests: migration's intended in-memory
contract preserves legacy [instance]/[fields] populated for
read-fallback during the transition (Tasks 7/8), and a clean
re-read post-save would lose those copies. The kept design splits
in-memory (env-merged) from save-back (file-only) without
disturbing the legacy-fields invariant.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 29 out of 29 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/main.rs Outdated
Comment thread tests/auth_refresh.rs Outdated
Comment thread tests/migration_legacy.rs Outdated
Robustness improvements:
- main.rs: --profile validation now happens inside `run()` instead
  of standalone in main(). This means a bad profile name flows
  through the unified error-reporting block, so `jr --profile
  bad:name --output json ...` callers get the structured
  {"error":..,"code":..} payload instead of a plain stderr line.
- tests/auth_refresh.rs: env_remove the full set of JR_INSTANCE_*
  vars (URL, AUTH_METHOD, CLOUD_ID, ORG_ID, OAUTH_SCOPES) plus
  JR_PROFILE / JR_DEFAULT_PROFILE. Previously, only
  JR_INSTANCE_AUTH_METHOD was cleared, so a parent shell with
  JR_INSTANCE_URL set would make the empty-config test look
  configured and assertions would fail on dev machines.
- tests/migration_legacy.rs: ENV_MUTEX.lock() now uses
  unwrap_or_else(|p| p.into_inner()) so a panic in one test doesn't
  poison the mutex and cascade-fail subsequent tests in the same
  binary. Matches the pattern used in other env-mutex helpers.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 29 out of 29 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/auth_profiles.rs Outdated
Comment thread tests/migration_legacy.rs Outdated
Comment thread docs/specs/multi-profile-auth.md Outdated
Comment thread docs/specs/multi-profile-auth.md Outdated
Test env hygiene:
- tests/auth_profiles.rs: jr() helper now scrubs the full set of JR_*
  env vars (JR_PROFILE/JR_DEFAULT_PROFILE/JR_INSTANCE_*/JR_FIELDS_*/
  JR_DEFAULTS_*/JR_BASE_URL/JR_AUTH_HEADER/JR_EMAIL/JR_API_TOKEN/
  JR_OAUTH_CLIENT_*) so direnv-set vars on dev machines can't make
  fresh-install tests look configured or trigger unintended legacy
  migration.
- tests/migration_legacy.rs: XdgConfigGuard now scrubs JR_* env vars
  (with prior-value restore on Drop) for the same reason. Extends the
  RAII guarantee to every var Config::load reads, not just
  XDG_CONFIG_HOME.

Spec drift:
- jr auth refresh now correctly documented: clears keychain entries
  (shared for api_token, per-profile for oauth) and re-runs the FULL
  login flow (oauth = browser-based 3LO, not silent refresh_token
  grant). The interactivity is intentional — same #207 macOS keychain
  ACL rebind that the api_token flow needs. --oauth flag is the
  explicit override that forces the OAuth path.
- Error table updated to reflect actual behavior:
    - login no-input requires URL is now broader ("target profile
      has no URL configured", not just "creating a new profile")
    - refresh on URL-less profile is the actual error condition,
      not the spec's previous "OAuth refresh not applicable" wording
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 29 out of 29 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

src/cache.rs:27

  • Cache corruption warnings emitted by read_cache only mention the filename (e.g., teams.json) but not the profile namespace. With per-profile caches, this can be ambiguous when the user has multiple profiles (it's not obvious which ~/.cache/jr/v1/<profile>/... is affected). Consider including the profile (or full path) in the warning message so users can locate and delete the right cache directory.
fn read_cache<T: DeserializeOwned + Expiring>(profile: &str, filename: &str) -> Result<Option<T>> {
    let path = cache_dir(profile).join(filename);
    let content = match std::fs::read_to_string(&path) {
        Ok(c) => c,
        Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
        Err(e) => return Err(e.into()),
    };
    let cache: T = match serde_json::from_str(&content) {
        Ok(c) => c,
        Err(e) => {
            eprintln!("warning: cache file {filename} unreadable ({e}); will refetch");
            return Ok(None);

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/api/auth.rs Outdated
Comment thread src/cli/auth.rs Outdated
- src/api/auth.rs: refresh_oauth_token doc comment now references
  [profiles.<name>].oauth_scopes (was [instance].oauth_scopes — old
  pre-multi-profile shape).
- src/cli/auth.rs::status: 'No profiles configured' fast-path now
  fires only when profile_arg is None (i.e., status with no
  --profile flag on a fresh install). When the user explicitly
  passes --profile X against an empty profiles map, take the strict
  path and error with 'unknown profile: X' — silent success would
  hide a typo and contradict switch/remove/logout's strict behavior.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 29 out of 29 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/config.rs
Comment thread src/api/auth.rs Outdated
- Config::save_global now reads file-only baseline from disk before
  saving, then overlays only the multi-profile fields
  (default_profile + profiles map) from in-memory `self.global`.
  Prevents transient JR_* env overrides from baking into config.toml
  when mutating commands run with env vars set (e.g.,
  JR_DEFAULTS_OUTPUT=json jr auth switch sandbox would have baked
  output = "json" into the saved file). Same shape as round-17's
  migration-time leak, but in the save path. Other top-level fields
  like defaults.output now pass through from disk unchanged.
- clear_all_credentials now only deletes the legacy flat OAuth keys
  (oauth-access-token / oauth-refresh-token) when "default" appears
  in the profiles list. Previously, jr auth refresh --profile sandbox
  in api_token flow would unconditionally wipe the default profile's
  intact-but-unmigrated OAuth tokens — destroying credentials for
  users mid-upgrade who hadn't yet triggered lazy migration.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 29 out of 29 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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