Skip to main content

Configuration Approach

NanoClaw Pro uses environment variables for runtime settings and in-code constants for behavior. Most configuration lives in src/config.ts.
Modify src/config.ts directly for default values, or use .env to override at runtime without changing code.

Environment Variables

Authentication

CLAUDE_CODE_OAUTH_TOKEN
string
OAuth token for Claude subscription (Pro/Max plans)Extract from ~/.claude/.credentials.json after logging in to Claude Code:
cat ~/.claude/.credentials.json | jq -r '.token'
Use either this OR ANTHROPIC_API_KEY, not both.
ANTHROPIC_API_KEY
string
Anthropic API key for pay-per-use billingGet from: https://console.anthropic.com/
.env
ANTHROPIC_API_KEY=sk-ant-api03-...
Use either this OR CLAUDE_CODE_OAUTH_TOKEN, not both.
ANTHROPIC_BASE_URL
string
default:"https://api.anthropic.com"
Custom API endpoint for third-party or self-hosted models
.env
ANTHROPIC_BASE_URL=https://your-api-endpoint.com
ANTHROPIC_AUTH_TOKEN=your-token-here
Useful for:
  • Open-source Claude-compatible models
  • Internal API proxies
  • Regional endpoints

Assistant Identity

ASSISTANT_NAME
string
default:"Andy"
The name of your assistantChanges:
  • Trigger pattern (messages must start with @YourName)
  • Response prefix (YourName: added to responses)
.env
ASSISTANT_NAME=Claw
After changing, rebuild and restart:
npm run build
launchctl kickstart -k gui/$(id -u)/com.nanoclaw  # macOS
ASSISTANT_HAS_OWN_NUMBER
boolean
default:"false"
Whether the assistant uses a dedicated phone numberSet to true if using a separate SIM/device for WhatsApp. Affects how the main channel is registered:
  • false: Self-chat mode (personal WhatsApp number)
  • true: Direct message mode (dedicated bot number)
.env
ASSISTANT_HAS_OWN_NUMBER=true

Container Runtime

CONTAINER_IMAGE
string
default:"nanoclaw-agent:latest"
Docker/Apple Container image name for agent execution
.env
CONTAINER_IMAGE=nanoclaw-agent:latest
Rebuild the image after code changes:
./container/build.sh
CONTAINER_TIMEOUT
number
default:"1800000"
Maximum agent execution time in milliseconds (default: 30 minutes)
.env
CONTAINER_TIMEOUT=600000  # 10 minutes
Agents that exceed this timeout are terminated. Increase for long-running tasks.
CONTAINER_MAX_OUTPUT_SIZE
number
default:"10485760"
Maximum container output size in bytes (default: 10MB)
.env
CONTAINER_MAX_OUTPUT_SIZE=52428800  # 50MB
Prevents memory issues from verbose agent logs.
IDLE_TIMEOUT
number
default:"1800000"
How long to keep containers alive after last result (default: 30 minutes)
.env
IDLE_TIMEOUT=900000  # 15 minutes
Keeping containers alive improves response time for follow-up messages (session continuity).
MAX_CONCURRENT_CONTAINERS
number
default:"5"
Maximum number of agent containers running simultaneously
.env
MAX_CONCURRENT_CONTAINERS=3
Prevents resource exhaustion when processing many groups. Messages queue if limit is reached.

Polling Intervals

These are defined in src/config.ts and cannot be overridden via .env (edit source if needed):
POLL_INTERVAL
number
default:"2000"
How often to check for new messages (milliseconds)
src/config.ts
export const POLL_INTERVAL = 2000;  // 2 seconds
SCHEDULER_POLL_INTERVAL
number
default:"60000"
How often to check for due scheduled tasks (milliseconds)
src/config.ts
export const SCHEDULER_POLL_INTERVAL = 60000;  // 1 minute
IPC_POLL_INTERVAL
number
default:"1000"
How often to check for IPC messages from containers (milliseconds)
src/config.ts
export const IPC_POLL_INTERVAL = 1000;  // 1 second

Timezone

TZ
string
default:"system timezone"
Timezone for scheduled tasks (cron expressions)
.env
TZ=America/New_York
Uses system timezone if not specified. See tz database for valid values.

Configuration File (src/config.ts)

All configuration constants are centralized in src/config.ts:
src/config.ts
import os from 'os';
import path from 'path';
import { readEnvFile } from './env.js';

// Read from .env file
const envConfig = readEnvFile(['ASSISTANT_NAME', 'ASSISTANT_HAS_OWN_NUMBER']);

export const ASSISTANT_NAME =
  process.env.ASSISTANT_NAME || envConfig.ASSISTANT_NAME || 'Andy';
  
export const ASSISTANT_HAS_OWN_NUMBER =
  (process.env.ASSISTANT_HAS_OWN_NUMBER ||
    envConfig.ASSISTANT_HAS_OWN_NUMBER) === 'true';

export const POLL_INTERVAL = 2000;
export const SCHEDULER_POLL_INTERVAL = 60000;

// Absolute paths (required for container mounts)
const PROJECT_ROOT = process.cwd();
const HOME_DIR = process.env.HOME || os.homedir();

export const MOUNT_ALLOWLIST_PATH = path.join(
  HOME_DIR,
  '.config',
  'nanoclaw',
  'mount-allowlist.json',
);

export const STORE_DIR = path.resolve(PROJECT_ROOT, 'store');
export const GROUPS_DIR = path.resolve(PROJECT_ROOT, 'groups');
export const DATA_DIR = path.resolve(PROJECT_ROOT, 'data');

// Container settings
export const CONTAINER_IMAGE =
  process.env.CONTAINER_IMAGE || 'nanoclaw-agent:latest';
  
export const CONTAINER_TIMEOUT = parseInt(
  process.env.CONTAINER_TIMEOUT || '1800000',
  10,
);

export const IDLE_TIMEOUT = parseInt(
  process.env.IDLE_TIMEOUT || '1800000',
  10,
);

export const MAX_CONCURRENT_CONTAINERS = Math.max(
  1,
  parseInt(process.env.MAX_CONCURRENT_CONTAINERS || '5', 10) || 5,
);

// Trigger pattern (based on assistant name)
function escapeRegex(str: string): string {
  return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

export const TRIGGER_PATTERN = new RegExp(
  `^@${escapeRegex(ASSISTANT_NAME)}\\b`,
  'i',
);

export const TIMEZONE =
  process.env.TZ || Intl.DateTimeFormat().resolvedOptions().timeZone;

Trigger Pattern Customization

The trigger pattern determines which messages activate Claude. Default: ^@Andy\b (case-insensitive)

Change Trigger Word

Set ASSISTANT_NAME:
.env
ASSISTANT_NAME=Bot
Now messages must start with @Bot:
  • @Bot hello
  • @bot what's up ✅ (case-insensitive)
  • Hey @Bot ❌ (must be at start)

Advanced Trigger Patterns

For complex patterns, modify src/config.ts directly:
src/config.ts
// Accept multiple trigger words
export const TRIGGER_PATTERN = new RegExp(
  `^@(Andy|Bot|Claude)\\b`,
  'i',
);

// Require exact case
export const TRIGGER_PATTERN = new RegExp(
  `^@Andy\\b`
);  // Remove 'i' flag

// Allow trigger anywhere in message
export const TRIGGER_PATTERN = new RegExp(
  `@Andy\\b`,
  'i',
);

Main Channel: No Trigger Required

The “main” channel (self-chat or admin DM) can be configured to not require a trigger. Set during registration:
npx tsx setup/index.ts --step register \
  --jid "<jid>" \
  --name "Main" \
  --folder "whatsapp_main" \
  --is-main \
  --no-trigger-required
Now all messages in that chat trigger Claude, not just those starting with @Andy.

Directory Paths

Paths are absolute (required for container mounts). Defined in src/config.ts:
const PROJECT_ROOT = process.cwd();
// /Users/yourname/nanoclaw-pro
Never use relative paths for container mounts. The container’s working directory differs from the host.

Applying Configuration Changes

After modifying configuration:
1

Sync .env to container

Claude authentication variables must be copied to the container mount:
mkdir -p data/env && cp .env data/env/env
2

Rebuild TypeScript

If you modified src/config.ts:
npm run build
3

Restart the service

# macOS (launchd)
launchctl kickstart -k gui/$(id -u)/com.nanoclaw

# Linux (systemd)
systemctl --user restart nanoclaw

Example .env File

.env
# Claude Authentication (choose ONE)
CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-...
# ANTHROPIC_API_KEY=sk-ant-api03-...

# Assistant Identity
ASSISTANT_NAME=Claw
ASSISTANT_HAS_OWN_NUMBER=false

# Container Settings
CONTAINER_IMAGE=nanoclaw-agent:latest
CONTAINER_TIMEOUT=1800000
IDLE_TIMEOUT=900000
MAX_CONCURRENT_CONTAINERS=3

# Timezone
TZ=America/Los_Angeles

# Optional: Custom API endpoint
# ANTHROPIC_BASE_URL=https://your-api-endpoint.com
# ANTHROPIC_AUTH_TOKEN=your-token

# Optional: Voice transcription
# OPENAI_API_KEY=sk-...

Next Steps

Container Configuration

Mount additional directories and configure isolation

Skills System

Add capabilities with pre-built skills

Build docs developers (and LLMs) love