11 KiB
Environment Configuration Schema
Status: Implemented and tested (25 test cases)
File: src/env.ts
Tests: src/tests/env.test.ts
Overview
SF uses 80+ SF_* environment variables to control behavior at startup and runtime. Previously, these were read directly from process.env throughout the codebase, leading to:
- Silent failures when config was missing (no errors, just wrong behavior)
- Type-unsafe access (IDE couldn't auto-complete, linters couldn't check)
- No documentation about what variables exist or what they do
- Scattered default logic (each module computed its own defaults)
This schema provides centralized, type-safe, validated access to all SF configuration.
Quick Start
Using the env schema
import { getCompleteSfEnv } from "./env";
// Get fully validated, type-safe environment config
const config = getCompleteSfEnv();
// IDE completion works:
config.SF_DEBUG; // boolean
config.SF_HOME; // string
config.sfHome; // computed default
config.stateDir; // computed default (SF_STATE_DIR or SF_HOME)
Setting variables
# Enable debug mode
export SF_DEBUG=1
# Set custom home directory
export SF_HOME=/opt/sf
# Disable RTK compression
export SF_RTK_DISABLED=1
# Enable the machine surface with prompt tracing
export SF_HEADLESS=1
export SF_HEADLESS_PROMPT_TRACE=1
Schema Categories
Core Paths (set by loader.ts)
SF_PKG_ROOT— Package installation root (where SF is installed)SF_BIN_PATH— Path to the SF executable (used for spawning)SF_VERSION— Package version from package.jsonSF_WORKFLOW_PATH— Path to bundled SF-WORKFLOW.mdSF_BUNDLED_EXTENSION_PATHS— Serialized extension manifestsSF_CODING_AGENT_DIR— PI SDK agent directory
Directories
All directory variables are optional and have sensible defaults:
SF_HOME(default:~/.sf) — Root state directorySF_STATE_DIR(default:SF_HOME) — Milestone/slice/task stateSF_WORKSPACE_BASE(default:SF_STATE_DIR/workspace) — User workspacesSF_HISTORY_BASE(default:SF_STATE_DIR/history) — Session historySF_NOTIFICATIONS_BASE(default:SF_STATE_DIR/notifications) — NotificationsSF_SCHEDULE_FILE(legacy import only; default:SF_STATE_DIR/schedule.jsonl) — pre-DB schedule queue compatibility inputSF_RECOVERY_BASE(default:SF_STATE_DIR/recovery) — Recovery artifactsSF_FORENSICS_BASE(default:SF_STATE_DIR/forensics) — DiagnosticsSF_SETTINGS_BASE(default:SF_STATE_DIR/settings) — User settings- And 5+ more for specific recovery/export/cleanup artifacts
Performance Tuning
SF_RTK_DISABLED(boolean: 0/1, default: 0) — Disable RTK compressionSF_RTK_PATH— Custom path to RTK tool (auto-detected)SF_RTK_REWRITE_TIMEOUT_MS(integer, default: 5000) — Timeout in msSF_CIRCUIT_BREAKER_OPEN_DURATION_MS(integer, default: 60000)SF_CIRCUIT_BREAKER_FAILURE_THRESHOLD(integer, default: 5)SF_CIRCUIT_BREAKER_HALF_OPEN_MAX_ATTEMPTS(integer, default: 2)SF_HEADLESS_PROMPT_TRACE_CHARS(integer, default: 1000)
Debug Flags
All debug flags are 0 or 1 (disabled or enabled):
SF_QUIET— Suppress startup bannerSF_DEBUG— Enable verbose loggingSF_DEBUG_EXTENSIONS— Enable extension debug loggingSF_TRACE_ENABLED— Collect execution tracesSF_HEADLESS— Suppress TUI for the machine surface, use stdio onlySF_HEADLESS_PROMPT_TRACE— Trace prompts in the machine surfaceSF_STARTUP_TIMING— Measure cold-start latencySF_SHOW_TOKEN_COST— Show LLM token costsSF_FIRST_RUN_BANNER— Show first-run welcomeSF_DISABLE_STARTUP_DOCTOR— Skip health checksSF_ENGINE_BYPASS— Use JS implementation instead of RustSF_DISABLE_NATIVE_SF_PARSER— Disable native parserSF_DISABLE_NATIVE_SF_GIT— Disable native git
Extensions
SF_SKILL_MANIFEST_STRICT(boolean) — Fail on invalid manifestsSF_PERMISSION_LEVEL(enum:full,restricted,sandbox, default:sandbox)SF_GEMINI_PERMISSION_MODE(enum:ask,auto,deny, default:ask)SF_SESSION_BROWSER_DIR— Override browser session directorySF_SESSION_BROWSER_CWD— Override browser working directorySF_FETCH_ALLOWED_URLS— Comma-separated list of allowed URLsSF_ALLOWED_COMMAND_PREFIXES— Comma-separated command prefixes
Recovery and Dispatch
SF_RECOVERY_DOCTOR_MODULE— Custom recovery doctor moduleSF_RECOVERY_FORENSICS_MODULE— Custom forensics moduleSF_RECOVERY_SCOPE(enum:unit,milestone,global, default:unit)SF_RECOVERY_SESSION_FILE— Recovery session state pathSF_RECOVERY_ACTIVITY_DIR— Recovery activity logsSF_PARALLEL_WORKER(boolean) — Enable parallel worker modeSF_WORKER_MODEL— Model for worker dispatchSF_MILESTONE_LOCK— Lock file for milestone operationsSF_SLICE_LOCK— Lock file for slice operationsSF_WORKTREE— Current git worktreeSF_CLI_WORKTREE— CLI worktree pathSF_CLI_WORKTREE_BASE— CLI worktree base directorySF_CLEANUP_BRANCHES(boolean, default: 1) — Enable branch cleanupSF_CLEANUP_SNAPSHOTS(boolean, default: 1) — Enable snapshot cleanup
Settings Modules
All optional (allow custom implementations):
SF_SETTINGS_BUDGET_MODULE— Custom budget settingsSF_SETTINGS_HISTORY_MODULE— Custom history settingsSF_SETTINGS_METRICS_MODULE— Custom metrics settingsSF_SETTINGS_PREFS_MODULE— Custom preferences settingsSF_SETTINGS_ROUTER_MODULE— Custom router settingsSF_WORKSPACE_MODULE— Custom workspace moduleSF_SESSION_MANAGER_MODULE— Custom session manager
Miscellaneous
SF_TRIAGE_SUFFIX(default:_triage) — Suffix for triaged issuesSF_PROJECT_ID— Current project ID (UUID)SF_DOCTOR_SCOPE(enum:fast,normal,deep, default:normal)SF_EXPORT_FORMAT(enum:json,csv,markdown, default:json)SF_TARGET_SESSION_NAME— Target session for testingSF_TARGET_SESSION_PATH— Target session path for testingSF_VISUALIZER_BASE— Visualization output directory
API Reference
getCompleteSfEnv(env?: NodeJS.ProcessEnv): CompleteSfEnv
Primary entry point. Returns fully validated environment configuration with computed defaults.
const config = getCompleteSfEnv();
// Type-safe access
console.log(config.SF_DEBUG); // boolean
console.log(config.SF_HOME); // string or undefined
console.log(config.sfHome); // string (computed default)
console.log(config.stateDir); // string (computed from SF_STATE_DIR || SF_HOME)
console.log(config.agentDir); // string (computed from SF_AGENT_DIR || SF_CODING_AGENT_DIR || sfHome/agent)
parseCompleteSfEnv(env?: NodeJS.ProcessEnv): CompleteSfEnv
Alternative: Parse environment with graceful degradation (doesn't throw on validation errors).
getSfEnv(env?: NodeJS.ProcessEnv): SfEnv
Backward-compatible: Parses minimal schema (original set of variables). Use getCompleteSfEnv() for new code.
getEnvValidationSummary(env?: NodeJS.ProcessEnv): { configured: string[], defaults: string[], total: number }
For diagnostics: Shows which variables are explicitly set vs using defaults.
const summary = getEnvValidationSummary();
console.log(`Configured: ${summary.configured.length}/${summary.total}`);
console.log(`Using defaults: ${summary.defaults.length}`);
Schema Design
Zod-based validation
Uses Zod for composable, type-safe schema definition:
// Boolean flags (0 or 1)
const booleanOneZero = z
.enum(["0", "1"])
.transform((value) => value === "1")
.optional();
// Positive integers (parsed from strings)
const positiveInteger = z
.string()
.transform((v) => parseInt(v, 10))
.pipe(z.number().int().positive());
// Enums with defaults
SF_PERMISSION_LEVEL: z.enum(["full", "restricted", "sandbox"]).optional()
Two-schema approach
Minimal schema (sfEnvSchema):
- Backward-compatible with existing code
- 8 essential variables
- Used by loader.ts, CLI entry points
Complete schema (completeSfEnvSchema):
- All 80+ known SF_* variables
- Organized by category
- Comprehensive validation and defaults
- Used by modules needing full environment access
Graceful degradation
If validation fails:
getCompleteSfEnv()returns partial config (missing fields undefined)- No throws (never blocks dispatch)
- Warnings logged to stderr if
SF_DEBUG=1 - Allows SF to run with misconfigured variables (degraded behavior)
Testing
All 25 tests passing. Coverage includes:
- Boolean flag parsing (0 → false, 1 → true)
- Enum validation (rejects invalid values)
- Integer parsing and validation (positive only)
- Default computation (SF_HOME, SF_STATE_DIR, agentDir)
- Fallback behavior (graceful degradation)
- Round-trip parsing consistency
# Run tests
npm run test:unit -- src/tests/env.test.ts
Migration Guide
For existing code reading process.env.SF_* directly
Before:
const debug = process.env.SF_DEBUG === "1";
const home = process.env.SF_HOME || join(homedir(), ".sf");
After:
import { getCompleteSfEnv } from "./env";
const config = getCompleteSfEnv();
const debug = config.SF_DEBUG; // already parsed boolean
const home = config.sfHome; // already computed default
For modules needing environment access
-
Import at module level:
import { getCompleteSfEnv } from "./env"; -
Call in initialization (not hot path):
const config = getCompleteSfEnv(); -
Pass config to functions instead of re-reading process.env
Why This Matters
Problem: Silent misconfiguration
# Typo in env var name (SF_DEBG instead of SF_DEBUG)
export SF_DEBG=1
# SF runs normally but without debug logging (silent failure)
sf run
Solution: Centralized validation catches mistakes early
const config = getCompleteSfEnv();
// Now SF knows all 80+ valid variable names
// Unknown variables can trigger warnings
Benefit: Type safety
// IDE auto-completion works
config.SF_DEBUG // ✓ recognized
config.SF_DEBG // ✗ compile error
config.unknownVar // ✗ compile error
// Future refactors are safe (rename variables with confidence)
Future Enhancements
- Config file support (.sfrc.json with env override)
- Env schema generation (export schema as JSON Schema for docs)
- Config diagnostics (sf doctor --env shows all settings)
- Secrets redaction (API keys not logged)
- Per-project overrides (project-specific .sf/.env)
See Also
src/env.ts— Implementationsrc/tests/env.test.ts— Test suite.nvmrc— Node.js version (requires Zod support)