Environment variables
Pythinker Code CLI uses environment variables to control a small number of runtime behaviors — relocating the data directory, turning off telemetry, and temporarily switching models without touching the config file.
Important: API keys are not configured here
Credential variables such as PYTHINKER_API_KEY, ANTHROPIC_API_KEY, and OPENAI_API_KEY are not read automatically from shell environment variables. Running export PYTHINKER_API_KEY=xxx in the terminal does not give any provider its key — they must be written in config.toml under [providers.<name>] or the [providers.<name>.env] sub-table.
The only exception is the PYTHINKER_MODEL_* family, which is an explicit channel that does read credentials from the shell — see Define a model from environment variables.
For background, see Config overrides: provider credentials.
Core paths
PYTHINKER_CODE_HOME
Overrides the data root directory; the default is ~/.pythinker-code. Once set, the config file, sessions, logs, OAuth credentials, and all other data land under the new path:
export PYTHINKER_CODE_HOME="/path/to/custom/pythinker-code"Make sure the directory is writable. Multiple
pythinkerinstances sharing the samePYTHINKER_CODE_HOMEwill share config and credential files.
For the complete data directory structure, see Data locations.
PYTHINKER_DISABLE_TELEMETRY
Set to 1 to turn off anonymous telemetry reporting (also accepts true, yes, y, case-insensitive):
export PYTHINKER_DISABLE_TELEMETRY=1PYTHINKER_MODEL_* family
Switch models temporarily without modifying config.toml — when PYTHINKER_MODEL_NAME is set, the CLI synthesizes a temporary provider in memory; the change does not persist after restart. See Define a model from environment variables.
PYTHINKER_CODE_CUSTOM_HEADERS
Attaches custom HTTP headers to every outbound model request — both LLM chat requests (across all provider protocols) and /models listing requests. Useful when a gateway routes by header, for example to pin a specific cluster:
export PYTHINKER_CODE_CUSTOM_HEADERS=$'X-Gateway-Cluster: my-cluster\nX-Custom-Tag: debug'The format mirrors ANTHROPIC_CUSTOM_HEADERS: newline-separated Name: Value lines. Names and values are trimmed, and lines without a colon are ignored.
Added
Added in 0.20.2.
Precedence: the Pythinker identity headers (
User-Agent,X-Msh-*) and a provider'scustom_headersinconfig.toml(see Config files) override same-named entries here. Authentication is protocol-dependent: on thepythinker,openai, andopenai_responsesprotocols an exactAuthorizationentry replaces the generated bearer token, while/modelslisting requests keep their own authentication. A case variant such asauthorizationis never treated as the same name — it is combined with the real header, which can break requests. Do not use this variable for authentication or other reserved headers. Usecustom_headerswhen headers need to differ per provider.
Provider credential key names (written in config.toml)
The key names below are not read directly from the shell — they are key names written inside the [providers.<name>.env] sub-table of config.toml, serving as fallback values for api_key / base_url. The CLI reads only from the config file, not from process.env.
This design lets you keep familiar key name conventions while centralizing secret management in the config file:
[providers.pythinker.env]
PYTHINKER_API_KEY = "sk-xxx"
PYTHINKER_BASE_URL = "https://api.moonshot.ai/v1"Key names per provider:
| Key | Applicable provider | Default |
|---|---|---|
PYTHINKER_API_KEY | Pythinker / PyModel | None |
PYTHINKER_BASE_URL | Pythinker / PyModel | https://api.moonshot.ai/v1 |
ANTHROPIC_API_KEY | Anthropic | None |
ANTHROPIC_BASE_URL | Anthropic | Follows Anthropic SDK default |
OPENAI_API_KEY | OpenAI (openai and openai_responses) | None |
OPENAI_BASE_URL | OpenAI (openai and openai_responses) | https://api.openai.com/v1 |
GOOGLE_API_KEY | Google GenAI, Vertex AI | None |
VERTEXAI_API_KEY | Vertex AI | None |
GOOGLE_CLOUD_PROJECT | Vertex AI | None |
GOOGLE_CLOUD_LOCATION | Vertex AI | None |
WARNING
GOOGLE_APPLICATION_CREDENTIALS (path to a service account JSON file) is the only exception that goes through the system environment variable mechanism — it is read by the Google SDK directly via the standard ADC flow, and the CLI does not participate. All other key names must be placed in the [providers.<name>.env] sub-table to take effect.
For the full provider type and field reference, see Providers and models.
Define a model from environment variables (PYTHINKER_MODEL_*)
Want to switch models for testing without touching config.toml? When PYTHINKER_MODEL_NAME is set, the CLI synthesizes a temporary provider and model alias from the PYTHINKER_MODEL_* variables in memory — nothing is written back to the config file. These variables take priority over default_model in config.toml, but the -m <alias> option at startup still has the highest priority.
export PYTHINKER_MODEL_NAME="test-model"
export PYTHINKER_MODEL_API_KEY="YOUR_API_KEY"
export PYTHINKER_MODEL_BASE_URL="https://api.example.com/v1"
export PYTHINKER_MODEL_MAX_CONTEXT_SIZE="262144"
export PYTHINKER_MODEL_CAPABILITIES="image_in,thinking"
pythinkerComplete variable list:
| Variable | Required | Purpose | Default |
|---|---|---|---|
PYTHINKER_MODEL_NAME | Yes (also the enable switch) | Model id sent to the API | — |
PYTHINKER_MODEL_API_KEY | Yes | API key | — |
PYTHINKER_MODEL_PROVIDER_TYPE | No | Provider type: pythinker, anthropic, openai | pythinker |
PYTHINKER_MODEL_BASE_URL | No | API base URL | Each type has its own default |
PYTHINKER_MODEL_MAX_CONTEXT_SIZE | No | Maximum context length (tokens) | 262144 (256 K) |
PYTHINKER_MODEL_CAPABILITIES | No | Comma-separated capability tags, unioned with auto-detected capabilities | image_in,thinking |
PYTHINKER_MODEL_DISPLAY_NAME | No | Name shown in /model | Falls back to PYTHINKER_MODEL_NAME |
PYTHINKER_MODEL_MAX_OUTPUT_SIZE | No | Per-request output cap (anthropic only); when set, overrides the built-in Claude ceiling | Model default |
PYTHINKER_MODEL_REASONING_KEY | No | Reasoning field name override (openai only) | Auto-detected |
PYTHINKER_MODEL_THINKING_EFFORT | No | Thinking effort level: low/medium/high/xhigh/max | — |
PYTHINKER_MODEL_ADAPTIVE_THINKING | No | Force adaptive thinking on or off (anthropic only) | Inferred from model name |
If PYTHINKER_MODEL_NAME is set but a required variable is missing, startup fails immediately with a clear error message.
Runtime switches
Switches that control the behavior of subsystems such as telemetry, background tasks, and the plugin marketplace:
| Variable | Purpose | Valid values |
|---|---|---|
PYTHINKER_DISABLE_TELEMETRY | Disable anonymous telemetry reporting | 1, true, yes, y (case-insensitive) |
PYTHINKER_CODE_PASSWORD | Set a parallel auth credential for the pythinker web local server, valid alongside the bearer token; recommended when binding the server beyond loopback — see Local server and API | Any non-empty string; when unset, only the token is valid |
PYTHINKER_CODE_BACKGROUND_KEEP_ALIVE_ON_EXIT | Whether to keep background tasks when the session closes; takes higher priority than config.toml. The default is to stop them on exit | Truthy: 1/true/yes/on; falsy: 0/false/no/off |
PYTHINKER_CODE_BACKGROUND_MAX_RUNNING_TASKS | Cap on concurrently running background tasks; takes higher priority than [background] max_running_tasks in config.toml (unset means no cap) | Positive integer; invalid values are ignored |
PYTHINKER_CODE_BACKGROUND_BASH_TASK_TIMEOUT_S | Default timeout (seconds) for background Bash tasks, also used to re-arm foreground commands moved to the background; takes higher priority than [task] bash_task_timeout_s (0 means no timeout) | Non-negative integer; invalid values are ignored |
PYTHINKER_CODE_BACKGROUND_PRINT_BACKGROUND_MODE | What pythinker -p does while background tasks are still pending after the main turn; takes higher priority than [task] print_background_mode | exit, drain, or steer; invalid values are ignored |
PYTHINKER_CODE_BACKGROUND_PRINT_WAIT_CEILING_S | Wall-clock ceiling (seconds) for the print-mode drain/steer wait; takes higher priority than [task] print_wait_ceiling_s | Positive integer; invalid values are ignored |
PYTHINKER_CODE_BACKGROUND_PRINT_MAX_TURNS | Maximum number of new turns triggered by background-task completions in print mode; takes higher priority than [task] print_max_turns | Positive integer; invalid values are ignored |
PYTHINKER_IMAGE_MAX_EDGE_PX | Longest-edge ceiling (px) for image compression; takes higher priority than [image] max_edge_px in config.toml (default 2000) | Positive integer; invalid values are ignored |
PYTHINKER_IMAGE_READ_BYTE_BUDGET | Per-image byte budget for model-initiated image reads (ReadMediaFile default reads); takes higher priority than [image] read_byte_budget in config.toml (default 262144, i.e. 256 KB) | Positive integer; invalid values are ignored |
PYTHINKER_CODE_PLUGIN_MARKETPLACE_URL | Override the plugin marketplace JSON loaded by /plugins; useful for dev loopback servers, staging CDN files, or alternate marketplace directories | Unset (no default catalog; unset means only built-in entries are shown); accepts http://, file:// URLs, and local paths |
PYTHINKER_CODE_AGENT_DYNAMIC_WORKFLOW_MAX_CONCURRENCY | Cap how many AgentDynamicWorkflow subagents run concurrently during the initial ramp; takes higher priority than [dynamic_workflow] max_concurrency in config.toml (unset means no cap) | Positive integer; invalid values fail fast |
PYTHINKER_CODE_AGENT_DYNAMIC_WORKFLOW_TIMEOUT_MS | Maximum wall-clock time (ms) for one AgentDynamicWorkflow subagent; takes higher priority than [dynamic_workflow] timeout_ms in config.toml (default 7200000, or 2 hours) | Non-negative integer (0 means no timeout); invalid values fall back to the config or default |
PYTHINKER_SUBAGENT_TIMEOUT_MS | Maximum wall-clock time (ms) a single Agent subagent may run, and the same limit for tower workers and reviewers; takes higher priority than [subagent] timeout_ms in config.toml (default 7200000, i.e. 2 hours) | Non-negative integer (0 means no timeout); invalid values fall back to the config or default |
PYTHINKER_CODE_IDENTITY_NAME | Display name the agent calls itself in the system prompt; takes higher priority than [identity] name in config.toml and is never written back to it | Any non-empty string; blank values read as unset |
PYTHINKER_CODE_IDENTITY_SLUG | Protocol identifier for the User-Agent product token sent to third-party providers and the MCP client name; takes higher priority than [identity] slug. Derived from the name when unset | Any non-empty string; normalized to lowercase with non-alphanumeric runs folded to - |
PYTHINKER_CODE_BUILTIN_PRODUCT_SKILLS | Whether the built-in skills documenting Pythinker Code itself are offered to the model; takes higher priority than builtin_product_skills in config.toml (default enabled) | Truthy: 1/true/yes/on; falsy: 0/false/no/off |
PYTHINKER_CODE_REPEAT_BREAKER | Whether repeating the same tool call many times in a row injects reminders and eventually force-stops the turn. Unset keeps this on | Truthy: 1/true/yes/on; falsy: 0/false/no/off; any other value is ignored |
PYTHINKER_CODE_TUI_FULL_SCREEN | Control the fullscreen TUI with a fixed prompt dock, scrollable transcript, mouse text selection, clickable links, transcript search, and a clickable jump-to-bottom control. Fullscreen is enabled by default | 0 restores the legacy inline UI; unset or any other value keeps fullscreen enabled |
PYTHINKER_CODE_DANGEROUS_COMMAND_GUARD | Override [permission].dangerous_command_guard. The guard asks before dangerous or unanalyzable Bash commands in interactive modes and blocks them in Auto mode | true or false; default true |
PYTHINKER_CODE_PERMISSION_MODE_REMINDER | Stop injecting the automatic permission-mode reminder (the context note that explains Auto mode) into the model context; set to a false value or an empty value to disable | Truthy keeps the reminder enabled; falsy: 0/false/no/off disables it; an empty value disables it |
PYTHINKER_CODE_EXPERIMENTAL_SUBAGENT_FORK | Enable the experimental fork parameter on the Agent and AgentDynamicWorkflow tools, letting the model start a subagent with a snapshot of the calling agent's conversation history instead of an empty context; the master PYTHINKER_CODE_EXPERIMENTAL_FLAG=1 also enables it | Truthy: 1/true/yes/on; falsy: 0/false/no/off |
PYTHINKER_CODE_EXPERIMENTAL_TOOL_SELECT | Experimental on-demand tool loading: tools of MCP servers marked deferred: true stay out of the top-level tool list and are loaded via select_tools; also requires the model to declare the dynamically_loaded_tools capability — see MCP | Truthy: 1/true/yes/on; falsy: 0/false/no/off |
PYTHINKER_CODE_EXPERIMENTAL_TOWER | Enable the experimental /tower command for workspace-wide subagent coordination; the master PYTHINKER_CODE_EXPERIMENTAL_FLAG=1 also enables it | Truthy: 1/true/yes/on; falsy: 0/false/no/off |
PYTHINKER_CODE_WATCH | Attach filesystem watchers that reload config and workspace files; higher priority than [watch] enabled (default false) | Truthy: 1/true/yes/on; falsy: 0/false/no/off |
PYTHINKER_CODE_SEARCH_WORKER | Run the global search index in a dedicated worker thread; takes higher priority than [database] search in config.toml (default true) | Truthy: 1/true/yes/on; falsy: 0/false/no/off |
PYTHINKER_CODE_PERSISTENCE_MINIDB_READMODEL | Use the minidb-backed read model for session indexing; takes higher priority than [database] base in config.toml (default true) | Truthy: 1/true/yes/on; falsy: 0/false/no/off |
PYTHINKER_MCP_STARTUP_TIMEOUT_MS | Global default connection timeout (ms) for all MCP servers; takes higher priority than [mcp] startup_timeout_ms in config.toml, but a per-server startupTimeoutMs in mcp.json still wins (default 30000) | Integer from 1 to 2147483647; invalid values are ignored |
PYTHINKER_MCP_TOOL_TIMEOUT_MS | Global default single tool-call timeout (ms) for all MCP servers; takes higher priority than [mcp] tool_timeout_ms in config.toml, but a per-server toolTimeoutMs in mcp.json still wins (default 60000) | Integer from 1 to 2147483647; invalid values are ignored |
PYTHINKER_LOOP_MAX_STEPS_PER_TURN | Maximum Agent steps per turn; takes higher priority than [loop_control] max_steps_per_turn in config.toml (unset or 0 means unlimited) | Non-negative integer; invalid values are ignored |
PYTHINKER_LOOP_MAX_ATTEMPTS_PER_STEP | Maximum total attempts for a failing step (including the initial attempt); takes higher priority than [loop_control] max_attempts_per_step in config.toml (default 10). The deprecated PYTHINKER_LOOP_MAX_RETRIES_PER_STEP is still honored with a warning when this variable is unset | Non-negative integer; invalid values are ignored |
PYTHINKER_CODE_INFINITE_RETRY | Retry every failed LLM request indefinitely — turn steps and background operations such as compaction alike — instead of failing the task; waits use exponential backoff (capped at 32 s) and honor the server's Retry-After header, and aborting still cancels immediately. Intended for long-running unattended evaluations against endpoints that may fail temporarily | Truthy: 1/true/yes/on; falsy: 0/false/no/off |
PYTHINKER_TOKEN_COUNTING_STRATEGY | Which context token count is reported externally (the context-size display); takes higher priority than [token_counting] strategy in config.toml (default measured+estimated) | measured+estimated, measured, estimated (case-insensitive); invalid values are ignored |
PYTHINKER_WEB_SEARCH_BASE_URL | API URL of the web search (WebSearch) service; takes higher priority than [services.pymodel_search] base_url in config.toml, and enables the service without that config section. Persisted credentials and custom headers are not forwarded to an env-selected endpoint | Non-blank string; blank values are ignored |
PYTHINKER_WEB_SEARCH_API_KEY | API key of the web search (WebSearch) service; replaces both the configured API key and OAuth credential when set | Non-blank string; blank values are ignored |
PYTHINKER_WEB_FETCH_BASE_URL | API URL of the web fetch (FetchURL) service; takes higher priority than [services.pymodel_fetch] base_url. Persisted credentials and custom headers are not forwarded to an env-selected endpoint | Non-blank string; blank values are ignored |
PYTHINKER_WEB_FETCH_API_KEY | API key of the web fetch (FetchURL) service; replaces both the configured API key and OAuth credential when set | Non-blank string; blank values are ignored |
PYTHINKER_CODE_EXPERIMENTAL_FLAG | Enable all registered experimental features for this process; a per-feature PYTHINKER_CODE_EXPERIMENTAL_<NAME> variable or an explicit entry in the [experimental] section of config.toml takes precedence over it; it does not select the agent engine | 1, true, yes, on |
PYTHINKER_CODE_LEGACY_FLAG | Use the legacy agent-core engine for pythinker, pythinker -p, pythinker export, and pythinker provider; these commands use agent-core-v2 by default | 1, true, yes, on |
PYTHINKER_SHELL_PATH | Override the Git Bash path on Windows (used when auto-detection fails) | Absolute path |
PYTHINKER_MODEL_MAX_COMPLETION_TOKENS | Hard cap on max_completion_tokens per LLM step; applies to the pythinker provider only | Positive integer; 0 or negative disables clamping |
PYTHINKER_MODEL_TEMPERATURE | Sampling temperature for every request; applies to the pythinker provider only (global — independent of PYTHINKER_MODEL_NAME) | Number, e.g. 0.3 |
PYTHINKER_MODEL_TOP_P | Nucleus-sampling top_p for every request; applies to the pythinker provider only (global) | Number, e.g. 0.95 |
PYTHINKER_MODEL_THINKING_EFFORT | Force a specific thinking effort on the wire (thinking.effort), bypassing the model's declared support_efforts; applies to the pythinker provider only, and only while Thinking is on | An effort value, e.g. max |
PYTHINKER_MODEL_THINKING_KEEP | Preserved-thinking passthrough; on pythinker sent as thinking.keep, on anthropic (Claude and Pythinker's Anthropic-compatible mode) sent as a context_management clear_thinking_20251015 edit (enabling keep routes Anthropic requests to the beta Messages API); overrides [thinking] keep (which defaults to "all"); only injected while Thinking is on | A value the API accepts, e.g. all; an off-value (false/0/no/off/none/null) disables it |
PYTHINKER_CODE_NO_AUTO_UPDATE | Fully disable the update preflight — no check, background install, or prompt. Legacy alias PYTHINKER_CLI_NO_AUTO_UPDATE is also honored | Truthy: 1/true/yes/on |
PYTHINKER_DISABLE_CRON | Disable the scheduled-task tool (CronCreate rejects new schedules; existing tasks do not fire) | 1 to disable |
The PYTHINKER_CODE_INFINITE_RETRY, PYTHINKER_CODE_IDENTITY_*, and PYTHINKER_CODE_BUILTIN_PRODUCT_SKILLS variables are read by the default agent-core-v2 engine. The legacy pythinker / pythinker -p path selected with PYTHINKER_CODE_LEGACY_FLAG=1 ignores them.
Diagnostic logs
These variables control log level and file rotation, read once at process startup:
| Variable | Purpose | Default |
|---|---|---|
PYTHINKER_LOG_LEVEL | Log level: off, error, warn, info, debug | info |
PYTHINKER_LOG_GLOBAL_MAX_BYTES | Maximum bytes per global log file | 6291456 (6 MB) |
PYTHINKER_LOG_GLOBAL_FILES | Number of global log files to retain | 5 |
PYTHINKER_LOG_SESSION_MAX_BYTES | Maximum bytes per session log file | 5242880 (5 MB) |
PYTHINKER_LOG_SESSION_FILES | Number of session log files to retain | 3 |
System environment variables
The CLI also reads several standard system variables to detect the runtime environment; it does not modify them:
HOME: used to resolve the default data pathVISUAL,EDITOR: external editor command (VISUALtakes precedence)PATH: used to locate dependencies such asrg,fd,fdfind, andgit; on Windows, Git Bash detection checks eachgit.exefound onPATH, including package-manager shims such as ScoopNO_COLOR,FORCE_COLOR: control color output (following the no-color.org convention)CI: when non-empty and not"0", disables theme detection and falls back to the dark themeTERM_PROGRAM,TERM,TMUX: detect terminal features and notification supportDISPLAY,WAYLAND_DISPLAY,XDG_SESSION_TYPE: detect Linux graphical sessions (for clipboard and image features)WSL_DISTRO_NAME,WSLENV: detect WSL for the clipboard PowerShell bridgeLOCALAPPDATA: used on Windows as a fallback when probing for the Git Bash installation path
HTTP proxy
Pythinker Code honors the standard proxy environment variables for all outbound traffic — model API calls, MCP servers, web tools, telemetry, sign-in, and update checks:
HTTP_PROXY/http_proxy: proxy forhttp://requestsHTTPS_PROXY/https_proxy: proxy forhttps://requestsALL_PROXY/all_proxy: fallback proxy used when the scheme-specific variable is unset; this is where a SOCKS proxy is usually setNO_PROXY/no_proxy: comma-separated hosts that bypass the proxy
Both HTTP(S) and SOCKS proxies are supported. A SOCKS proxy is recognized by its scheme — socks5://, socks5h://, socks4://, or socks:// (an alias for socks5://) — and is typically set via ALL_PROXY (the form used by tools like Clash and V2RayN). An HTTP(S) proxy takes precedence over ALL_PROXY for HTTP/HTTPS traffic.
The proxy is applied only when one of these variables is set; otherwise connections are made directly. Loopback hosts (localhost, 127.0.0.1, ::1) always bypass the proxy, so a local server such as a localhost MCP server keeps working when a proxy is configured — add your own internal hosts to NO_PROXY to exempt them too.
Stdio MCP servers that run as Node child processes honor HTTP_PROXY / HTTPS_PROXY / NO_PROXY automatically when the child's Node version supports NODE_USE_ENV_PROXY (Node ≥ 22.21 or ≥ 24.5); SOCKS proxying applies to Pythinker Code's own traffic only.
Next steps
- Config overrides — how environment variables, CLI options, and the config file interact by priority
- Data locations — directory structure affected by
PYTHINKER_CODE_HOME - Providers and models — full connection examples per provider type