Skip to content

Configuration

Z.E.N. tries hard to need no configuration at all. Install it, fire a workflow, and you'll never open a config file. The settings exist for the moments you want to change something: pick a different default provider, point Z.E.N. at a Postgres database instead of the local SQLite, restrict the Slack bot to a specific user, or run two projects with different defaults.

Three layers, in increasing order of specificity: defaults baked into the binary, a YAML file in ~/.zen/config.yaml (your machine), and a YAML file in .zen/config.yaml (this project). Environment variables override all three. The lower layers fill in what the higher ones don't set; you only write the keys you want to change.

Advanced: full configuration reference

Z.E.N. supports a layered configuration system with sensible defaults, optional YAML config files, and environment variable overrides. For a quick introduction, see Getting Started: Configuration.

Directory Structure

User-Level (~/.zen/)

~/.zen/
├── workspaces/owner/repo/  # Project-centric layout
│   ├── source/             # Clone or symlink -> local path
│   ├── worktrees/          # Git worktrees for this project
│   ├── artifacts/          # Workflow artifacts
│   └── logs/               # Workflow execution logs
├── zen.db               # SQLite database (when DATABASE_URL not set)
└── config.yaml             # Global configuration (optional)

Repository-Level (.zen/)

.zen/
├── commands/       # Custom commands
│   └── plan.md
├── workflows/      # Workflow definitions (YAML files)
└── config.yaml     # Repo-specific configuration (optional)

Configuration Priority

Settings are loaded in this order (later overrides earlier):

  1. Defaults - Sensible built-in defaults
  2. Global Config - ~/.zen/config.yaml
  3. Repo Config - .zen/config.yaml in repository
  4. Environment Variables - Always highest priority

Global Configuration

Create ~/.zen/config.yaml for user-wide preferences:

yaml
# Default provider
defaultAssistant: claude # or 'codex'

# Assistant defaults
assistants:
  claude:
    model: sonnet
    settingSources:   # Which CLAUDE.md files the SDK loads (default: ['project'])
      - project       # Project-level CLAUDE.md (always recommended)
      - user          # Also load ~/.claude/CLAUDE.md (global preferences)
  codex:
    model: gpt-5.3-codex
    modelReasoningEffort: medium
    webSearchMode: disabled
    additionalDirectories:
      - /absolute/path/to/other/repo

# Streaming preferences per platform
streaming:
  telegram: stream # 'stream' or 'batch'
  slack: batch
  github: batch

# Custom paths (usually not needed)
paths:
  workspaces: ~/.zen/workspaces
  worktrees: ~/.zen/worktrees

# Concurrency limits
concurrency:
  maxConversations: 10

# Env-leak gate bypass (last resort; weakens a security control)
# allow_target_repo_keys: false  # Set true to skip the env-leak-gate
                                 # globally for all codebases on this machine.
                                 # `env_leak_gate_disabled` is logged once per
                                 # process per source. See security.md.

Repository Configuration

Create .zen/config.yaml in any repository for project-specific settings:

yaml
# provider for this project (used as default provider for workflows)
assistant: claude

# Assistant defaults (override global)
assistants:
  claude:
    model: sonnet
    settingSources:  # Override global settingSources for this repo
      - project
  codex:
    model: gpt-5.3-codex
    webSearchMode: live

# Commands configuration
commands:
  folder: .zen/commands
  autoLoad: true

# Worktree settings
worktree:
  baseBranch: main  # Optional: auto-detected from git when not set
  copyFiles:  # Optional: Additional files to copy to worktrees
    - .env.example -> .env  # Rename during copy
    - .vscode               # Copy entire directory

# Documentation directory
docs:
  path: docs  # Optional: default is docs/

# Defaults configuration
defaults:
  loadDefaultCommands: true   # Load app's bundled default commands at runtime
  loadDefaultWorkflows: true  # Load app's bundled default workflows at runtime

# Concurrency policy (opt-in; default off)
concurrency:
  allowParallelWorkflows: false   # When true, different workflow names can run
                                  # in parallel on the same cwd. The dispatch
                                  # lock scopes by (working_path, workflow_name)
                                  # instead of working_path alone. Same-name
                                  # back-to-back dispatches still block
                                  # themselves. Useful for fleets that share a
                                  # cwd with `worktree.enabled: false`.

# Default subprocess timeouts for bash/script nodes (ms).
# Per-node `timeout:` in the workflow yaml always overrides these.
# Values must be positive integers ≤ 2147483647 (≈24.8 days); invalid values
# are ignored and the built-in 2-minute default applies.
timeouts:
  bash: 600000                    # 10 minutes for bash nodes
  script: 900000                  # 15 minutes for script nodes

# Per-project environment variables for workflow execution (Claude SDK only)
# Injected into the Claude subprocess env. Use the Web UI Settings panel for secrets.
# env:
#   MY_API_KEY: value
#   CUSTOM_ENDPOINT: https://...

# Per-repo override for the env-leak-gate bypass.
# Set to `false` to re-enable the gate for THIS repo even when the global
# config has `allow_target_repo_keys: true`. Set to `true` to grant the
# bypass for THIS repo only. Wins over the global flag in either direction.
# allow_target_repo_keys: false

Claude settingSources

Controls which CLAUDE.md files the Claude Agent SDK loads during sessions:

ValueDescription
projectLoad the project's CLAUDE.md (default, always included)
userAlso load ~/.claude/CLAUDE.md (user's global preferences)

Default: ['project']; only project-level instructions are loaded.

Set in global or repo config:

yaml
assistants:
  claude:
    settingSources:
      - project
      - user

This is useful when you maintain coding style or identity preferences in ~/.claude/CLAUDE.md and want Z.E.N. sessions to respect them.

Default behavior: When a workflow opts into a worktree via worktree.enabled: true, the .zen/ directory is copied automatically (artifacts, plans, workflows). Use copyFiles only for additional files like .env or .vscode.

Defaults behavior: The app's bundled default commands and workflows are loaded at runtime and merged with repo-specific ones. Repo commands/workflows override app defaults by name. Set defaults.loadDefaultCommands: false or defaults.loadDefaultWorkflows: false to disable runtime loading.

Base branch behavior: Before creating a worktree, the canonical workspace is synced to the latest code. Resolution order:

  1. If worktree.baseBranch is set: Uses the configured branch. Fails with an error if the branch doesn't exist on remote (no silent fallback).
  2. If omitted: Auto-detects the default branch via git remote show origin. Works without any config for standard repos.
  3. If auto-detection fails and a workflow references $BASE_BRANCH: Fails with an error explaining the resolution chain.

Docs path behavior: The docs.path setting controls where the $DOCS_DIR variable points. When not configured, $DOCS_DIR defaults to docs/. Unlike $BASE_BRANCH, this variable always has a safe default and never throws an error. Configure it when your documentation lives outside the standard docs/ directory (e.g., packages/docs).

Environment Variables

Environment variables override all other configuration. They are organized by category below.

Core

VariableDescriptionDefault
ZEN_HOMEBase directory for all Z.E.N.-managed files~/.zen
PORTHTTP server listen port3090 (auto-allocated in worktrees)
LOG_LEVELLogging verbosity (fatal, error, warn, info, debug, trace)info
BOT_DISPLAY_NAMEBot name shown in batch-mode "starting" messagesZ.E.N.
DEFAULT_AI_ASSISTANTDefault provider (claude or codex)claude
MAX_CONCURRENT_CONVERSATIONSMaximum concurrent AI conversations10
SESSION_RETENTION_DAYSDelete inactive sessions older than N days30

AI Providers; Claude

VariableDescriptionDefault
CLAUDE_USE_GLOBAL_AUTHUse global auth from claude /login (true/false)Auto-detect
CLAUDE_CODE_OAUTH_TOKENExplicit OAuth token (alternative to global auth);
CLAUDE_API_KEYExplicit API key (alternative to global auth);
TITLE_GENERATION_MODELLightweight model for generating conversation titlesSDK default
ZEN_CLAUDE_FIRST_EVENT_TIMEOUT_MSTimeout (ms) before Claude subprocess is considered hung (throws with diagnostic log)60000

When CLAUDE_USE_GLOBAL_AUTH is unset, Z.E.N. auto-detects: it uses explicit tokens if present, otherwise falls back to global auth.

AI Providers; Codex

VariableDescriptionDefault
CODEX_ID_TOKENCodex ID token (from ~/.codex/auth.json);
CODEX_ACCESS_TOKENCodex access token;
CODEX_REFRESH_TOKENCodex refresh token;
CODEX_ACCOUNT_IDCodex account ID;

Platform Adapters; Slack

VariableDescriptionDefault
SLACK_BOT_TOKENSlack bot token (xoxb-...);
SLACK_APP_TOKENSlack app-level token for Socket Mode (xapp-...);
SLACK_ALLOWED_USER_IDSComma-separated Slack user IDs for whitelistOpen access
SLACK_STREAMING_MODEStreaming mode (stream or batch)batch

Platform Adapters; Telegram

VariableDescriptionDefault
TELEGRAM_BOT_TOKENTelegram bot token from @BotFather;
TELEGRAM_ALLOWED_USER_IDSComma-separated Telegram user IDs for whitelistOpen access
TELEGRAM_STREAMING_MODEStreaming mode (stream or batch)stream

Platform Adapters; GitHub

VariableDescriptionDefault
GITHUB_TOKENGitHub personal access token (also used by gh CLI);
GH_TOKENAlias for GITHUB_TOKEN (used by GitHub CLI);
WEBHOOK_SECRETHMAC SHA-256 secret for GitHub webhook signature verification;
GITHUB_ALLOWED_USERSComma-separated GitHub usernames for whitelist (case-insensitive)Open access
GITHUB_BOT_MENTION@mention name the bot responds to in issues/PRsFalls back to BOT_DISPLAY_NAME

Database

VariableDescriptionDefault
DATABASE_URLPostgreSQL connection string (omit to use SQLite)SQLite at ~/.zen/zen.db

Web UI

VariableDescriptionDefault
WEB_UI_ORIGINCORS origin for API routes (restrict when exposing publicly)* (allow all)
WEB_UI_DEVWhen set, skip serving static frontend (Vite dev server used instead);

Worktree Management

VariableDescriptionDefault
STALE_THRESHOLD_DAYSDays before an inactive worktree is considered stale14
MAX_WORKTREES_PER_CODEBASEMax worktrees per codebase before auto-cleanup25
CLEANUP_INTERVAL_HOURSHow often the background cleanup service runs6

Docker / Deployment

VariableDescriptionDefault
ZEN_DATAHost path for Z.E.N. data (workspaces, worktrees, artifacts)Docker-managed volume
DOMAINPublic domain for Caddy reverse proxy (TLS auto-provisioned);
CADDY_BASIC_AUTHCaddy basicauth directive to protect Web UI and APIDisabled
AUTH_USERNAMEUsername for form-based auth (Caddy forward_auth);
AUTH_PASSWORD_HASHBcrypt hash for form-based auth password (escape $ as $$ in Compose);
COOKIE_SECRET64-hex-char secret for auth session cookies;
AUTH_SERVICE_PORTPort for the auth service container9000
COOKIE_MAX_AGEAuth cookie lifetime in seconds86400

.env File Locations

Infrastructure configuration (database URL, platform tokens) is stored in .env files:

ComponentLocationPurpose
CLI~/.zen/.envGlobal infrastructure config; CWD .env keys stripped before loading (no override needed)
Server (dev)<zen-repo>/.env + ~/.zen/.envRepo .env for platform tokens; ~/.zen/.env loaded with override: true
Server (binary)~/.zen/.envSingle source of truth (repo .env path is not available in compiled binaries)

How it works: At startup, the CLI strips all keys that Bun auto-loaded from the current working directory (from .env, .env.local, .env.development, .env.production) before loading ~/.zen/.env. This ensures CWD repo keys are fully removed rather than merely overridden. Target repo env vars cannot reach AI subprocesses; SUBPROCESS_ENV_ALLOWLIST blocks all non-whitelisted keys.

Best practice: Use ~/.zen/.env as the single source of truth:

bash
# Create global config
mkdir -p ~/.zen
cp .env.example ~/.zen/.env
# Edit with your values

Docker Configuration

In Docker containers, paths are automatically set:

/.zen/
├── workspaces/owner/repo/
│   ├── source/
│   ├── worktrees/
│   ├── artifacts/
│   └── logs/
└── zen.db

Environment variables still work and override defaults.

Command Folder Detection

When cloning or switching repositories, Z.E.N. looks for commands in this priority order:

  1. .zen/commands/ - Always searched first
  2. Configured folder from commands.folder in .zen/config.yaml (if specified)

Example .zen/config.yaml:

yaml
commands:
  folder: .claude/commands/zen  # Additional folder to search
  autoLoad: true

Examples

Minimal Setup (Using Defaults)

No configuration needed. Z.E.N. works out of the box with:

  • ~/.zen/ for all managed files
  • Claude as default provider
  • Platform-appropriate streaming modes

Custom AI Preference

yaml
# ~/.zen/config.yaml
defaultAssistant: codex

Project-Specific Settings

yaml
# .zen/config.yaml in your repo
assistant: claude  # Workflows inherit this provider unless they specify their own
commands:
  autoLoad: true

Custom storage path

Set ZEN_HOME to relocate workspaces, the database, and config to a different directory. Beta users running Docker get the full container reference as part of their onboarding kit.

Streaming Modes

Each platform adapter supports two streaming modes, configured via environment variable or ~/.zen/config.yaml.

Stream Mode

Messages are sent in real-time as the AI generates responses.

ini
TELEGRAM_STREAMING_MODE=stream
SLACK_STREAMING_MODE=stream

Pros:

  • Real-time feedback and progress indication
  • More interactive and engaging
  • See AI reasoning as it works

Cons:

  • More API calls to platform
  • May hit rate limits with very long responses
  • Creates many messages/comments

Best for: Interactive chat platforms (Telegram)

Batch Mode

Only the final summary message is sent after AI completes processing.

ini
TELEGRAM_STREAMING_MODE=batch
SLACK_STREAMING_MODE=batch

Pros:

  • Single coherent message/comment
  • Fewer API calls
  • No spam or clutter

Cons:

  • No progress indication during processing
  • Longer wait for first response
  • Can't see intermediate steps

Best for: Issue trackers and async platforms (GitHub)

Platform Defaults

PlatformDefault Mode
Telegramstream
Slackbatch
GitHubbatch
Web UISSE streaming (always real-time, not configurable)

Concurrency Settings

Control how many conversations the system processes simultaneously:

ini
MAX_CONCURRENT_CONVERSATIONS=10  # Default: 10

How it works:

  • Conversations are processed with a lock manager
  • If the max concurrent limit is reached, new messages are queued
  • Prevents resource exhaustion and API rate limits
  • Each conversation maintains its own independent context

Tuning guidance:

ResourcesRecommended Setting
Low resources3-5
Standard10 (default)
High resources20-30 (monitor API limits)

Health Check Endpoints

The application exposes health check endpoints for monitoring:

Basic Health Check:

bash
curl http://localhost:3090/health

Returns: {"status":"ok"}

Database Connectivity:

bash
curl http://localhost:3090/health/db

Returns: {"status":"ok","database":"connected"}

Concurrency Status:

bash
curl http://localhost:3090/health/concurrency

Returns: {"status":"ok","active":0,"queued":0,"maxConcurrent":10}

Use cases:

  • Docker healthcheck configuration
  • Load balancer health checks
  • Monitoring and alerting systems (Prometheus, Datadog, etc.)
  • CI/CD deployment verification

Troubleshooting

Config Parse Errors

If your config file has invalid YAML syntax, you'll see error messages like:

[Config] Failed to parse global config at ~/.zen/config.yaml: <error details>
[Config] Using default configuration. Please fix the YAML syntax in your config file.

Common YAML syntax issues:

  • Incorrect indentation (use spaces, not tabs)
  • Missing colons after keys
  • Unquoted values with special characters

The application will continue running with default settings until the config file is fixed.

AI that follows a recipe, not a conversation.