Decision: One resolution order for every credential
Status: implemented Implemented: 2026-09-30 Related: 2026-09-30-unified-config-keys.md, 2026-09-29-region-command-level-option.md, 2026-09-29-config-init-wizard.md, 2026-09-02-dual-auth-mode.md
Problem
A credential could be set in a function argument, a process environment variable, ./.env, and ~/.abx, and each caller picked a different winner. Sandbox.create stopped at the first .env file it found, so a project file that did not contain the sandbox key hid ~/.abx/.env. Sandbox.deploy never read ~/.abx at all. abx config list ignored ./.env and ignored vendor LLM variables when a stored llm_api_key existed. abx mcp install parsed ~/.abx/.env by hand and could return SANDBOX_API_KEY ahead of E2B_API_KEY. Public docs also disagreed: getting started listed the code parameter as the lowest priority, and the region notes wrote abx config set region and SANDBOX_REGION as one tier.
Decision
Every credential is resolved in one order. The first non-blank value wins:
- Explicit argument for this call (
api_key=,llm_api_key=,openai_base_url=,openai_model=,--token, command--region). - Process environment, in the per-key order below.
./.env, same names.~/.abx: secrets in~/.abx/.env(abx config set), endpoint / model / region in~/.abx/config.toml.- Built-in default, or unset when the key has none.
.env files merge per key. ./.env overrides ~/.abx/.env only for a key it sets. A key the project file omits still comes from ~/.abx/.env. A blank or whitespace-only value is unset and does not hide the next layer.
abx config list labels the winner (env) for the process environment, (user) for either file, (default) for a built-in default, and (not set) when nothing applies. Secrets stay masked.
| Need | Process environment, first hit wins | ~/.abx when the layers above are unset |
Default |
|---|---|---|---|
Sandbox API key (sandbox_api_key; SDK field stays api_key) |
E2B_API_KEY, then SANDBOX_API_KEY |
E2B_API_KEY in ~/.abx/.env |
none |
| Alibaba Cloud AK/SK | ALICLOUD_ACCESS_KEY_*, then AccessKey / AccessSecret |
the same names in ~/.abx/.env |
none |
| GitHub token | GITHUB_TOKEN |
GITHUB_TOKEN in ~/.abx/.env |
none |
| LLM API key | ABX_LLM_API_KEY, BAILIAN_CODING_PLAN_API_KEY, DASHSCOPE_API_KEY, OPENAI_API_KEY, then legacy ABX_QWEN_CODE_API_KEY |
ABX_LLM_API_KEY in ~/.abx/.env, then legacy llm_api_key in config.toml, then ABX_QWEN_CODE_API_KEY in ~/.abx/.env |
none |
| LLM base URL | ABX_LLM_BASE_URL, then OPENAI_BASE_URL |
llm_base_url in config.toml, then legacy qwen_code_base_url |
DashScope compatible-mode |
| LLM model | ABX_LLM_MODEL, then OPENAI_MODEL |
llm_model in config.toml, then legacy qwen_code_model |
qwen3-coder-plus |
| Region | SANDBOX_REGION |
region in config.toml |
cn-hangzhou |
Callers that share this order:
Sandbox.createand the other SDK clients go throughagent_sandbox.transport.config.load_config.agent_sandbox.api.sandbox._build_infrapasses that result intocreate_auth_provider. The auth helper does not walk~/.abxitself.Sandbox.deployusesagent_sandbox.api.deploy.resolve_llm_env.api/deploy.pydoes not import the CLI.- Natural-language
abx createusesresolve_coding_agent_credentials, which callsconfig_cmd._effective_valueand then passes the winner intoresolve_qwen_code_credentialsasstored_api_key. That lower function treats the argument as already chosen. abx template install/abx template searchuseresolve_github_token(--tokenis the explicit layer).abx mcp installcopies the sandbox key fromload_config().api_key. Theapi_urlandregionit embeds into the editor config are still taken only from the process environment; the laterabx mcp startprocess loads config again.resolve_regionand every regional command useload_config, soSANDBOX_REGIONbeatsabx config set region.
The user-facing copy of this table is Credential resolution and 凭证解析.
API Design
load_config(**overrides) -> TransportConfig
overrides > process env > merged .env files > ~/.abx/config.toml > defaults
project_dotenv_value(names: tuple[str, ...]) -> str | None
first non-blank name in ./.env only
resolve_llm_env(*, llm_api_key, openai_base_url, openai_model) -> dict[str, str]
explicit > process env > ./.env > ~/.abx > DashScope default
raises DeployLLMKeyMissingError (E7001) when no key exists
resolve_github_token(explicit: str | None) -> str | None
--token > process GITHUB_TOKEN > ./.env > ~/.abx/.env > None
_effective_value(key) -> (value, "env" | "user" | "default" | "unset")
TransportConfig.api_key and Sandbox.create(api_key=) stay. The CLI name is sandbox_api_key.
Alternatives considered
- Process environment only, with
~/.abxas a write-only store. Rejected: a developer who ranabx config setand then opened a new shell with no exports would appear to have no key. ~/.abxbeats the process environment. Rejected: CI and a one-offexportmust be able to override the machine store without editing it.- First existing
.envfile wins as a whole. Rejected: a project.envused for unrelated tools hid the sandbox key stored in~/.abx/.env. - A new
(project)source label inabx config list. Rejected: both files are user configuration.(env)stays reserved for the process environment so the label still answers "will this shell override the files?" - Teach
resolve_qwen_code_credentialsto re-read the environment after it receivesstored_api_key. Rejected: the CLI already resolved the winner. A second pass would let a vendor variable override an explicit stored argument that a direct caller passed on purpose.
Dependencies
agent_sandbox.transport.config, agent_sandbox.cli.commands.config_cmd, agent_sandbox.api.deploy, agent_sandbox.cli.commands._coding_agent, agent_sandbox.cli.commands.mcp, agent_sandbox.cli.region. python-dotenv reads the .env files. Deploy must not import the CLI.
Test Strategy
tests/integration/test_credential_priority_e2e.py points the CLI, load_config, resolve_llm_env, the coding agent, resolve_github_token, resolve_region, MCP's editor-config writer, and ACR namespace resolution at one temporary home and one project .env. It checks the process environment beating both files, a project file that omits a key still yielding ~/.abx, and a key set in ./.env beating ~/.abx and losing to the process environment. It also checks same-layer order (E2B_API_KEY over SANDBOX_API_KEY, ABX_LLM_API_KEY over DASHSCOPE_API_KEY, ALICLOUD_ACCESS_KEY_ID over AccessKey), a command --region beating SANDBOX_REGION, a blank value falling through, a legacy ABX_QWEN_CODE_API_KEY, and --acr-namespace over ACR_NAMESPACE over ./.env over ~/.abx. An explicit llm_api_key= and --token still win. MCP copies SANDBOX_REGION into the editor config only when the process environment sets it.
tests/test_transport/test_config.py covers the per-key .env merge. tests/test_cli/test_config_commands.py covers a whitespace GITHUB_TOKEN falling through to the stored token. tests/test_cli/test_region_options.py already asserts SANDBOX_REGION beats config.toml.
The suite isolates transport.config._PROJECT_ENV_FILE so a developer's ./.env cannot leak into abx config list.
Acceptance criteria
- With
E2B_API_KEYexported and a different key in~/.abx/.env,abx config list,load_config().api_key, andabx mcp installall use the exported key. - With neither the process environment nor
./.envsettingABX_LLM_API_KEY,Sandbox.deployand natural-languageabx createuse the key stored byabx config set llm_api_key. - A
./.envthat sets onlySANDBOX_HTTP_TIMEOUTdoes not hideE2B_API_KEYin~/.abx/.env. DASHSCOPE_API_KEYin the process environment beats a storedllm_api_key.ABX_LLM_API_KEYbeatsDASHSCOPE_API_KEY.SANDBOX_REGIONbeatsregionin~/.abx/config.toml. A command--regionbeats both.- A whitespace-only
GITHUB_TOKENdoes not hide the stored token.