Skip to content
Crystal CodeDocs
ENEnglish

Environment variables

The process environment selects the data directory and can supply API keys. It does not replace config.json or providers.json.

Variable Meaning
CRYSTAL_HOME Data directory instead of ~/.crystal
<PROVIDER>_API_KEY Default key variable for a provider name
apiKeyEnvironment Optional providers.json field that replaces the default variable name
CRYSTAL_API_KEY Shared fallback when the provider requires a key and no earlier variable is set
XDG_CONFIG_HOME Config root for the OpenCode instruction and skill fallbacks. Unset uses ~/.config
PATH Inherited by bash. The host does not invent a path

Do not pass secrets on the command line. They appear in process lists.

CrystalHome.Resolve picks the root in this order:

  1. An explicit root argument, when the caller passes one.
  2. CRYSTAL_HOME, when it is set and not whitespace. The value is passed through Path.GetFullPath and is not trimmed.
  3. <user profile>/.crystal.

If the user profile directory is unavailable and neither of the first two applies, startup throws The user profile directory is not available.

EnsureCreated creates the root plus sessions, logs, plugins, and tools. Other well-known files (config.json, providers.json, credentials.json, and the rest) are created when the feature that owns them first writes.

For one provider, CredentialStore stops at the first non-whitespace value:

  1. The variable named by apiKeyEnvironment, when that field is set. Otherwise <NAME>_API_KEY: the provider name uppercased, hyphens turned into underscores. deepseek is DEEPSEEK_API_KEY. my-provider is MY_PROVIDER_API_KEY.
  2. The derived <NAME>_API_KEY again, when apiKeyEnvironment named something else. A custom name does not hide the derived name.
  3. CRYSTAL_API_KEY, only when the provider requires an API key.
  4. apiKey in providers.json, when set.
  5. credentials.json under the data directory, key <provider>.apiKey.

Environment values are trimmed. An empty or whitespace variable is skipped.

requiresApiKey defaults to false for ollama and true for every other protocol. If no key is required and none was found, resolution succeeds with an empty key. If a key is required and every source missed, startup reports the provider name, the apiKey forms, the environment names, and the credentials file path. /model leaves the current model unchanged when the selected provider has no key.

apiKey is not read until the environment sources above are empty.

Form Behavior
Literal text Used as the key after trim
{env:NAME} Reads NAME. Missing or whitespace fails with Environment variable NAME is not set.
{file:path} Reads the whole file and trims it. ~ and ~/... expand to the user profile. A relative path is under the data directory, not the workspace. Missing file: API key file not found: <path>. Empty file: API key file is empty: <path>.

{env:NAME} is independent of <PROVIDER>_API_KEY. Use it when the secret already lives in a different variable.

Saving a key writes credentials.json and, on Unix, sets the file to owner read and write only. The save does not export the key into the environment.

Instruction discovery and skill discovery share one rule. If XDG_CONFIG_HOME is set and not whitespace, that directory is the config root. Otherwise the root is <user profile>/.config.

Instructions then look for <config>/opencode/AGENTS.md as a global fallback, after the files in the Crystal data directory. Skills look under <config>/opencode as one OpenCode-compatible scan root. Project files under the workspace do not use this variable. See Prompts and instructions and Skills.