Skip to main content
.env is the single source of truth. It is loaded automatically at startup (process.loadEnvFile), is gitignored, and is validated at boot. A missing or invalid required value fails fast with an actionable error. Start from .env.example and set the required values. Everything else has a working default:

Variables

DEFAULT_MODEL, DEFAULT_AGENT, and APPROVAL_MODE are seeded into the settings table on the first boot only. Once a value exists there, it is authoritative and re-reading .env will not overwrite it.

Multiple guilds

Celly deploys its command set to every guild in DISCORD_GUILD_IDS and runs boot subscribe and thread reconcile across all of them. At startup each configured guild is fetched; a guild the bot cannot see (for example, it was never invited) is skipped with a warning. Startup fails only when no configured guild is reachable, or when the bot token itself is invalid. A single guild failing command deployment does not abort startup. Config.guildId remains available as guildIds[0] for code paths that still expect one guild.
Access control is global: ACCESS_ROLE_ID, BLOCK_ROLE_ID, and OWNER_ROLE_ID apply to every configured guild. Role IDs only match in the guild that owns them, and each guild’s owner (or a member with Manage Guild / Administrator) is always allowed in that guild. Per-guild role configuration is not supported in this version.
SESSION_BUDGET_USD is the global seed. /budget set <usd> stores budget_usd:<channelId> in the settings table; that value wins for the channel, and /budget set 0 disables the budget for it.

PROJECTS_ROOT and path containment

PROJECTS_ROOT is the only directory Celly will mount. A project’s host directory must resolve (realpath, case-insensitive) to a path inside PROJECTS_ROOT. Anything outside is rejected before a sandbox is created. On top of containment, a sensitive-path denylist rejects any project directory that overlaps:
  • The bot repository itself (process.cwd()).
  • DATA_DIR.
  • Under the user profile: .ssh, .aws, .gnupg, .config, .docker, .kube, .azure, .npmrc, .netrc, .celly, and AppData.
  • System directories (C:\Windows, System32, Program Files, ProgramData, or /etc, /usr, /bin, /sbin, /var, /opt, /System, /Library on POSIX).
Because containment is checked both ways, you also cannot register a parent of PROJECTS_ROOT or of any denied path. PROJECTS_ROOT is intentionally not runtime-editable; changing it means editing .env and restarting.
Keep secrets out of project directories. A mounted project is treated as untrusted content, and the agent can read anything inside it (git remotes, .env files, tokens). Use the sandbox credential proxy instead of committing credentials.

DATA_DIR

DATA_DIR holds the SQLite database (bot.db), the rotating log (bot.log), per-project server logs (logs/<sandbox>.log), and the single-instance lock. It is gitignored and created on boot.
Keep DATA_DIR outside OneDrive/Dropbox-style synced folders. Cloud-sync detection is not implemented in v1, so this is on you.

Idle auto-stop

With IDLE_STOP_MINUTES greater than 0 (default 30), Celly stops a project’s sandbox after that many minutes without message, prompt, or !shell activity. Projects with a run in flight and projects still provisioning are skipped. Celly posts a plain notice in the project channel; the next message wakes the project again. Set IDLE_STOP_MINUTES=0 to keep every started project running.

Admin page, backups, and log rotation

ADMIN_PORT starts a loopback-only HTTP status page and JSON API on 127.0.0.1. It has no authentication; reach it locally or through an SSH tunnel, never through a public interface. Backups run every BACKUP_INTERVAL_HOURS hours into DATA_DIR/backups and keep BACKUP_KEEP files. Logs rotate at LOG_MAX_BYTES and keep LOG_MAX_FILES numbered copies.