Configuration
RondoFlow reads all of its configuration from a single .env file at the project root. Every package — the UI, the server, and shared code — loads from that one file, so there is just one place to look.
npm run setup generates .env from .env.example and fills in a random BETTER_AUTH_SECRET for you. You then edit it to add Claude credentials and any optional providers/nodes.
There are two ways to set values:
- The
.envfile — best for the required server settings (database, auth, networking) and for first boot. - Settings → Credentials in the app — an encrypted, in-app credentials manager for provider keys, OAuth secrets, and SMTP. Values saved here are stored encrypted in the database and override the matching
.envvalue at boot. More on this below.
Not everything that the credentials manager handles ships in the generated .env. .env.example scaffolds Claude, OAuth, and SMTP, but not the OpenAI or Perplexity keys — those are normally set through Settings → Credentials. See Optional providers.
Required variables
These three must be present for the server to start and authenticate users.
| Variable | Purpose |
|---|---|
DATABASE_URL | PostgreSQL connection string. Default: postgresql://rondoflow:rondoflow_dev@localhost:5432/rondoflow. |
BETTER_AUTH_SECRET | Secret used by Better Auth (and to encrypt stored credentials). Must be set — there is no hardcoded fallback. |
BETTER_AUTH_URL | Public URL of the auth/server endpoint. Default: http://localhost:3001. |
DATABASE_URL=postgresql://rondoflow:rondoflow_dev@localhost:5432/rondoflow
BETTER_AUTH_SECRET=<random-hex>
BETTER_AUTH_URL=http://localhost:3001BETTER_AUTH_SECRET has no default. If it is missing the server throws on boot: “BETTER_AUTH_SECRET environment variable is required. Run npm run setup to generate one.” The same secret is also used to encrypt credentials saved through the in-app manager, so do not rotate it casually once secrets are stored.
Networking
These set where the server listens and which origin it trusts. The defaults work for local development; change them when you serve the UI or API from a different host or port.
| Variable | Default | What it does |
|---|---|---|
PORT | 3001 | Port the Fastify server listens on. |
UI_ORIGIN | http://localhost:3000 | Origin allowed for CORS and Socket.IO, and added to Better Auth’s trusted origins. Must match where the UI is actually served. |
PORT=3001
UI_ORIGIN=http://localhost:3000If UI_ORIGIN does not match the URL the browser loads the UI from, API calls and the live Socket.IO connection are blocked by CORS and sign-in is rejected. Keep it in sync with your deployment.
Claude authentication
Assistants (agents) run through the Claude Code CLI, which the server starts (spawns) as a child process. You must provide one of two credentials:
| Variable | What it is |
|---|---|
ANTHROPIC_API_KEY | A standard Anthropic API key, billed per token. |
CLAUDE_CODE_OAUTH_TOKEN | A setup token from claude setup-token, which uses your Claude subscription. |
# Provide ONE of the two
ANTHROPIC_API_KEY=
CLAUDE_CODE_OAUTH_TOKEN=To get a setup token, run:
claude setup-tokenAuth precedence: if both are set, the setup token (CLAUDE_CODE_OAUTH_TOKEN) wins, and only that winning credential is forwarded to the spawned CLI. The other is not passed along. Set only the one you intend to use to avoid surprises.
Optional providers
RondoFlow can also run assistants on OpenAI and Perplexity, and run Sakana AI transform nodes. Add a key for whichever you want to use — leave the rest unset.
| Variable | Used by |
|---|---|
OPENAI_API_KEY | OpenAI assistants. |
PERPLEXITY_API_KEY | Perplexity assistants. |
SAKANA_API_KEY | Sakana AI transform node (sakana-ai). |
OPENAI_API_KEY and PERPLEXITY_API_KEY are not in .env.example; the normal way to set them is Settings → Credentials (groups OpenAI access / Perplexity access), where they are stored encrypted in the database. They also work as .env variables, but you must add the lines yourself.
SAKANA_API_KEY is present in .env.example and is read directly from the server environment when a Sakana AI node runs.
If an assistant is configured for a provider whose key is missing, the run fails with a clear message — for example “OPENAI_API_KEY is not configured. Add it in Settings → Credentials (OpenAI access).” See Providers for per-provider details.
OAuth sign-in (optional)
Leave these empty to disable the corresponding sign-in button. When set, RondoFlow rebuilds its auth layer automatically, so GitHub/Google sign-in starts working without a server restart.
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=GitHub
Create an OAuth app under GitHub → Settings → Developer settings → OAuth Apps, then paste the Client ID and Client Secret. This enables “Sign in with GitHub”.
Email node (SMTP)
The canvas Email card (node) sends a workflow’s combined output as an HTML email. It is opt-in — the card ships disabled, so building a workflow never sends mail until you turn it on. To make it work, configure an SMTP transport with these variables:
| Variable | Default | What it does |
|---|---|---|
SMTP_HOST | unset | SMTP server hostname. Required — leave blank to disable email entirely. |
SMTP_FROM | unset | Sender address, e.g. RondoFlow <noreply@example.com>. Required. |
SMTP_PORT | 587 | SMTP port. |
SMTP_SECURE | false | true for port 465 (implicit TLS); false for 587 (STARTTLS). When unset, defaults to true only if the port is 465. |
SMTP_USER | unset | SMTP username. Auth is only attached when both user and password are present, so unauthenticated relays / local catchers (e.g. MailHog) work without it. |
SMTP_PASS | unset | SMTP password (encrypted when stored via the credentials manager). |
SMTP_HOST=
SMTP_PORT=587
SMTP_SECURE=false
SMTP_USER=
SMTP_PASS=
SMTP_FROM=Email is considered configured only when both SMTP_HOST and SMTP_FROM are set. If either is missing the Email card stays inert and surfaces a clear “Email is not configured. Set SMTP_HOST and SMTP_FROM.” message rather than a cryptic transport error.
SMTP can also be managed at runtime in Settings → Credentials (group SMTP); a DB-stored value overrides .env at boot, just like the other credentials. The Email node and its API are covered in the Data nodes guide and the Workflows guide.
First-admin bootstrap
RondoFlow is invite-only — self-registration is disabled, so an administrator creates every account. These variables seed the very first admin so you are not locked out of a fresh install. See Users & Roles for the role model and inviting people.
| Variable | Default | What it does |
|---|---|---|
RONDOFLOW_ADMIN_EMAIL | unset | Email of the first admin account to create. |
RONDOFLOW_ADMIN_PASSWORD | unset | Password for that account (8–128 characters). |
RONDOFLOW_ADMIN_NAME | Administrator | Display name for the account. |
RONDOFLOW_ADMIN_EMAIL=
RONDOFLOW_ADMIN_PASSWORD=
RONDOFLOW_ADMIN_NAME=AdministratorThis runs at seed time — during npm run db:seed (and npm run setup, which seeds). It is idempotent: if the account already exists it just ensures the role is admin.
Both RONDOFLOW_ADMIN_EMAIL and RONDOFLOW_ADMIN_PASSWORD must be set, or the bootstrap is skipped. Set them before seeding, then rotate the password after your first login. The repository’s docker-compose.yml only provisions PostgreSQL — migrations and seeding run on the host via npm scripts (npm run setup, or npm run db:migrate then npm run db:seed), so run the seed step yourself with these variables set to create the first admin.
Tuning variables
These adjust runtime behavior. All are optional and have sensible defaults.
| Variable | Default | What it does |
|---|---|---|
MAX_CONCURRENT_AGENTS | 5 | Maximum number of assistants the server runs at once. Additional runs queue. |
CLAUDE_CODE_MAX_OUTPUT_TOKENS | 128000 | Max output tokens per assistant response. Claude Code clamps this to each model’s true max, so the high default is safe and helps avoid truncated results. |
RONDOFLOW_DEBUG_SPAWN | unset | Set to 1 (or true) to log a full per-event trace of each spawned CLI process. Useful for debugging empty or failed runs. |
IS_SANDBOX | unset | Set to 1 to mark the environment as sandboxed. Required when running as root with bypass-permissions; the server also sets this automatically for that exact case. |
Seeing empty assistant output? Set RONDOFLOW_DEBUG_SPAWN=1 and check the trace. A common cause is running as root with bypass permissions without IS_SANDBOX=1. See Monitoring for more.
Spawn timeouts & teardown
These protect against a hung Claude CLI process (a stalled network, an unanswerable permission prompt, an infinite loop) and control whether in-flight runs survive a disconnect. All times are in milliseconds.
| Variable | Default | What it does |
|---|---|---|
RONDOFLOW_SPAWN_IDLE_TIMEOUT_MS | 300000 | Kills a run that streams no event for this long. Resets on every stream event, so it is safe for long-lived interactive agents and only fires on a true stall. Applies to every spawn. Set 0 to disable. |
RONDOFLOW_SPAWN_MAX_MS | 0 | Absolute wall-clock cap applied to every spawn regardless of activity. Off (0) by default; one-shot generators set their own caps. |
RONDOFLOW_TEARDOWN_ON_DISCONNECT | 1 | When a user’s last tab disconnects, tear down their in-flight runs after the grace window. Set to 0 to keep runs alive across a disconnect (only server shutdown tears them down then). |
RONDOFLOW_TEARDOWN_GRACE_MS | 60000 | Grace window before disconnect teardown fires. A refresh, new tab, or reconnect within this window cancels it. |
The idle timeout is what reaps stalled headless runs — for example a scheduled plan-mode prompt that never produces output. See Schedules for how scheduled runs behave.
External folders
There are also optional variables for External Folders — EXTERNAL_FOLDERS_HOST_PATH (default ./external) and EXTERNAL_FOLDERS_CONTAINER_ROOT (default /external) — covered in that guide.
Spend cap (not an env var)
Looking for a budget variable? The global per-run spend cap (forwarded to the CLI as --max-budget-usd) is not an environment variable. It is the “Max Budget” setting in the app, stored on the canonical global Safety Rule (policy) row and managed via the budget endpoint. null means no cap; the maximum is 1000 USD. See Security for how budgets resolve.
In-app credentials manager
Open Settings → Credentials to manage provider keys, OAuth secrets, and SMTP without editing files. It is backed by the /api/settings/credentials route and stores values encrypted in the database.
It manages the same keys as .env, grouped by provider:
| Group | Fields |
|---|---|
| Claude access | Claude API key, Claude setup token |
| OpenAI access | OpenAI API key |
| Perplexity access | Perplexity API key |
| GitHub OAuth | Client ID, Client Secret |
| Google OAuth | Client ID, Client Secret |
| SMTP | SMTP host, SMTP port, SMTP secure, SMTP username, SMTP password (secret), SMTP from address |
How it behaves:
- Masked, never returned in full. The status endpoint reports only whether each credential is set and a masked preview (for example
••••a1b2). Secrets are never sent back to the browser. Non-secret values (the OAuth Client IDs, the SMTP host/port/secure/username/from) are shown in full so you can confirm them. - Source is labeled. Each field shows whether the value is Saved (the API source
db— stored in the database) or From .env (the sourceenv— read from the file), so you always know where a value comes from. - Database overrides
.env. A value saved in the manager takes precedence over the matching.envvariable at boot and on save. Most changes apply immediately — saving a provider key takes effect on the next run, and saving OAuth credentials rebuilds the auth layer without a restart. - Clear to fall back. Clearing a field deletes the stored value and restores the original
.envvalue (if any).
Stored secrets are encrypted with BETTER_AUTH_SECRET. An override key is supported, but the encryption module currently reads it from the lowercase variable rondoflow_SECRET (not RONDOFLOW_SECRET) — on case-sensitive systems the uppercase name is ignored and encryption silently falls back to BETTER_AUTH_SECRET. For most installs, just rely on BETTER_AUTH_SECRET. If that secret changes, previously stored credentials can no longer be decrypted and the manager falls back to the .env values.