gitbutlerapp/gitbutler · error

Pass either --api-key or --api-key-env, not both

Error message

Pass either --api-key or --api-key-env, not both

What it means

`resolve_secret_input` normalizes the two mutually exclusive ways of supplying an API secret non-interactively: `--api-key <value>` (inline) and `--api-key-env <var>` (read from environment). Passing both is treated as a caller mistake because the intended secret source is ambiguous, so it bails before reading either value.

Solutions

  1. Remove either --api-key or --api-key-env, keeping only one secret source
  2. Prefer --api-key-env in scripts to avoid leaking the key into shell history and process listings
  3. Audit wrapper scripts/aliases that may inject a default --api-key flag

Example fix

// before
but config ai --provider openai --api-key sk-1 --api-key-env OPENAI_API_KEY
// after
but config ai --provider openai --api-key-env OPENAI_API_KEY
Defensive patterns

Strategy: validation

Validate before calling

function assertSingleSecretSource({ apiKey, apiKeyEnv }) {
  const n = [apiKey, apiKeyEnv].filter(Boolean).length;
  if (n > 1) throw new Error('Pass either --api-key or --api-key-env, not both');
}

Type guard

const hasExactlyOneSecret = (o) => ['apiKey','apiKeyEnv'].filter(k => o[k] != null).length === 1;

Try / catch

try {
  await configureAi(args);
} catch (e) {
  if (e.message.includes('not both')) {
    console.error('Strip one of --api-key / --api-key-env from your command template');
  } else throw e;
}

Prevention

When it happens

Trigger: Invoking `but config ai ... --api-key X --api-key-env MY_VAR` (or the SDK equivalent passing both Some values to ai_config_non_interactive).

Common situations: Shell scripts accumulating flags from templates or defaults so both options end up set; users unsure which flag to use and passing both; config generators merging CLI profiles.

Understand the failure class

Background: "mutually exclusive" flag errors: what "can't supply both nx and xx", "--raw is not compatible with -i" and "cannot be used with" mean, and how to fix them — this error's family across 29 libraries.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18). Data as JSON: /api/errors/9214f8529c2c3d6f. Report an issue: GitHub.

Appendix: source

Thrown at crates/but/src/command/config.rs:1660

            t.sym().success,
            t.config_value.paint(provider.display_name()),
            t.hint.paint(scope.as_str())
        )?;
    } else if let Some(out) = out.for_json() {
        out.write_value(serde_json::json!({
            "provider": provider.as_git_config_value(),
            "scope": scope.as_str(),
        }))?;
    }
    Ok(())
}

fn resolve_secret_input(
    api_key: Option<String>,
    api_key_env: Option<String>,
) -> Result<Option<Sensitive<String>>> {
    if api_key.is_some() && api_key_env.is_some() {
        anyhow::bail!("Pass either --api-key or --api-key-env, not both")
    }

    if let Some(value) = api_key {
        return Ok(Some(Sensitive(value)));
    }

    if let Some(env_name) = api_key_env {
        let value = std::env::var(&env_name)
            .with_context(|| format!("Environment variable '{env_name}' is not set"))?;
        return Ok(Some(Sensitive(value)));
    }

    Ok(None)
}

fn require_non_interactive_secret_if_byok(
    key_option: AiKeyOption,
    secret: Option<&Sensitive<String>>,

View on GitHub (pinned to 58e5313667)