Skip to content

fix(cli): preserve profile path_prefix when BOXLITE_API_KEY is set#629

Open
G4614 wants to merge 1 commit into
boxlite-ai:mainfrom
G4614:fix/cli-api-key-preserve-path-prefix
Open

fix(cli): preserve profile path_prefix when BOXLITE_API_KEY is set#629
G4614 wants to merge 1 commit into
boxlite-ai:mainfrom
G4614:fix/cli-api-key-preserve-path-prefix

Conversation

@G4614
Copy link
Copy Markdown
Contributor

@G4614 G4614 commented May 29, 2026

when setting BOXLITE_API_KEY, URL/path_prefix should not be changed

Test plan

Two-sided (reverted vs applied) via three unit tests on the pure resolve_rest_options helper, the reverted side toggling it back to the bare-options logic:

  • api_key_env_preserves_profile_path_prefix — profile carries path_prefix=acme + an ambient BOXLITE_API_KEY: resolved options must keep path_prefix = Some("acme") (the regression).
  • api_key_env_overrides_profile_bearer_but_keeps_prefix — confirmed precedence: the bearer resolves to the env key while the prefix still survives.
  • api_key_env_without_profile_has_no_prefix — no profile → no routing slot even with a key (single-tenant shape unaffected).
resolve_rest_options(profile{prefix:"acme"}, env_key) pre-fix (bare opts in env-key branch) post-fix (start from profile)
opts.path_prefix None → URL /v1/boxes → 404 Some("acme")/v1/acme/boxes
bearer credential env key env key (unchanged)

Precedence is unchanged where it already worked: BOXLITE_API_KEY still wins for the bearer and --url / --path-prefix flags still override the profile — the env key simply no longer discards the profile's url and routing slot.

GlobalFlags::create_runtime built a bare BoxliteRestOptions in the
BOXLITE_API_KEY branch and applied only the api key, skipping
into_rest_options(profile) — the sole place the selected profile's
path_prefix (routing slot) is threaded. With path_prefix = None the URL
builder emitted the prefix-less /v1/boxes shape, which a multi-tenant
server rejects with 404, so `--profile p1 list` failed whenever the env
key was exported.

Extract a pure resolve_rest_options(stored, env_api_key) that starts from
the profile (keeping url + path_prefix) and lets BOXLITE_API_KEY override
ONLY the bearer credential. Add unit tests covering the three precedence
cases.

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

G4614 commented May 29, 2026

Root cause — exactly which code dropped path_prefix

The bug lived entirely in the old GlobalFlags::create_runtime (src/cli/src/cli.rs:247-260 on main):

let mut opts = BoxliteRestOptions::new(url);   // bare: path_prefix = None, credential = None

// BOXLITE_API_KEY env beats stored credentials of any kind.
if let Ok(key) = std::env::var("BOXLITE_API_KEY") && !key.is_empty() {
    opts = opts.with_api_key(key);                          // ← taken whenever the env key is set
} else if let Some(profile) = stored {
    opts = crate::credentials::into_rest_options(profile);  // ← the ONLY code that copies path_prefix
    opts.url = self.url.clone().unwrap_or(opts.url);
}

into_rest_options(profile) is the only place the profile's path_prefix is carried into opts (it calls .with_path_prefix(...)). But it sits on the else if branch. The instant BOXLITE_API_KEY is present, the if branch runs instead, so opts stays the bare BoxliteRestOptions::new(url) and its path_prefix is never set — it remains None.

The later --path-prefix override (the if let Some(path_prefix) = self.path_prefix … block just below) only fires when the --path-prefix flag / BOXLITE_REST_PATH_PREFIX env is set; it does not recover the profile's value. So with an ambient BOXLITE_API_KEY and a --profile whose credential carries a path_prefix, the URL builder emits the empty-prefix /v1/boxes shape and a multi-tenant server answers 404.

In one line: the credential axis (BOXLITE_API_KEY) and the routing axis (path_prefix) were entangled in a single if/else — selecting the env-key branch silently discarded the profile's url + path_prefix instead of overriding only the bearer.

The fix in this PR decouples them: always start opts from into_rest_options(profile) (so url + path_prefix survive), then let BOXLITE_API_KEY overwrite only opts.credential.

@G4614 G4614 marked this pull request as ready for review May 29, 2026 12:53
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