17 KiB
Environment Configuration Guide
Guide for collecting complete environment information and generating harness/config/environment.json during harness creation.
Overview
environment.json is the contract between ecl-harness-engineer and harness-executor. It describes everything the executor needs to know to start the application, set up dependencies, and run verification — but it does NOT define what to verify. Verification configuration (verify.json) is dynamically generated by harness-executor at task runtime.
Key principle: ecl-harness-engineer answers "what does this project need to run?" — harness-executor answers "what should we check after making changes?"
environment.json Schema (v2.0)
{
"version": "2.0",
"project": {
"name": "my-project",
"type": "web-api | cli | frontend | library | hybrid",
"language": "go | typescript | python | java | rust",
"description": "Brief project description"
},
"runtime": {
"language": "go",
"version": "1.22",
"package_manager": "go | npm | pnpm | yarn | pip | poetry | uv | maven | gradle",
"build_command": "go build ./...",
"test_command": "go test ./...",
"lint_command": "make lint-arch"
},
"startup": {
"command": "go run ./cmd/server",
"args": ["--port", "${PORT:-8080}"],
"working_dir": ".",
"env": {
"PORT": "${PORT:-8080}",
"ENV": "development",
"LOG_LEVEL": "${LOG_LEVEL:-debug}"
},
"readiness": {
"type": "http",
"config": {
"endpoint": "/health",
"port": "${PORT:-8080}",
"expected_status": 200,
"timeout_seconds": 30,
"poll_interval_ms": 500
}
}
},
"services": [
{
"name": "postgres",
"type": "database",
"required": true,
"image": "postgres:15",
"ports": ["5432:5432"],
"env": {
"POSTGRES_USER": "${DB_USER:-postgres}",
"POSTGRES_PASSWORD": "${DB_PASSWORD}",
"POSTGRES_DB": "${DB_NAME:-app}"
},
"health_check": "pg_isready -U ${DB_USER:-postgres}",
"connection": {
"url_env": "DATABASE_URL",
"default_url": "postgres://${DB_USER:-postgres}:${DB_PASSWORD}@localhost:5432/${DB_NAME:-app}?sslmode=disable"
},
"setup": {
"migration_command": "go run ./cmd/migrate up",
"seed_command": "go run ./cmd/seed"
}
}
],
"env_vars": {
"required": {
"DATABASE_URL": {
"purpose": "PostgreSQL connection string",
"sensitive": true,
"example": "postgres://user:pass@localhost:5432/dbname"
},
"JWT_SECRET": {
"purpose": "JWT token signing key",
"sensitive": true,
"test_value_ok": true,
"test_value": "test-secret-do-not-use-in-production"
}
},
"optional": {
"PORT": {
"purpose": "HTTP server port",
"default": "8080",
"sensitive": false
},
"LOG_LEVEL": {
"purpose": "Logging verbosity",
"default": "info",
"sensitive": false
}
}
},
"endpoints": {
"health": "/health",
"base_url": "http://localhost:${PORT:-8080}"
},
"scripts": {
"setup": "harness/scripts/setup-env.sh",
"start": "harness/scripts/start-server.sh",
"teardown": "harness/scripts/teardown-env.sh"
},
"_meta": {
"generated_by": "ecl-harness-engineer",
"generated_at": "2026-03-30T10:00:00Z",
"schema_version": "2.0",
"requires_user_input": ["DATABASE_URL", "JWT_SECRET"],
"todos": [
"Confirm Redis connection if caching is needed"
]
}
}
Detection Strategy (4-Step Pipeline)
Step 1: Detect Project Type and Language
# Language detection (high confidence)
test -f go.mod && echo "go"
test -f package.json && echo "typescript/javascript"
test -f pyproject.toml && echo "python"
test -f requirements.txt && echo "python"
test -f Cargo.toml && echo "rust"
test -f pom.xml && echo "java-maven"
test -f build.gradle && echo "java-gradle"
# Project type detection (medium confidence)
# Server indicators
grep -rq "http.ListenAndServe\|gin.Default\|chi.NewRouter\|echo.New" --include="*.go" . && echo "web-api"
grep -q '"express"\|"fastify"\|"koa"\|"hono"\|"nest"' package.json 2>/dev/null && echo "web-api"
grep -rq "FastAPI\|Flask\|Django" --include="*.py" . && echo "web-api"
# CLI indicators
test -d cmd/cli && echo "cli"
grep -rq "cobra\|urfave/cli" --include="*.go" . && echo "cli"
grep -q '"commander"\|"yargs"\|"oclif"' package.json 2>/dev/null && echo "cli"
# Frontend indicators
grep -q '"react"\|"vue"\|"svelte"\|"next"\|"nuxt"' package.json 2>/dev/null && echo "frontend"
# Library indicators (no entry point, exports only)
grep -q '"main"\|"bin"' package.json 2>/dev/null || echo "library"
Step 2: Detect Startup Command
Priority order — use the first successful detection:
| Priority | Source | Command |
|---|---|---|
| 1 | Existing harness/config/environment.json |
jq .startup.command environment.json |
| 2 | Dockerfile CMD/ENTRYPOINT | `grep -E "^(CMD |
| 3 | docker-compose.yml command | grep "command:" docker-compose.yml |
| 4 | Makefile targets | `grep -E "^(run |
| 5 | package.json scripts | jq '.scripts.start // .scripts.dev' package.json |
| 6 | Go cmd/ directory | ls cmd/*/main.go → go run ./cmd/<name> |
| 7 | Python main module | test -f main.py && echo "python main.py" |
| 8 | User confirmation tool | Required if all auto-detection fails; in Codex use request_user_input |
# Go: Detect startup command
if test -d cmd/; then
SERVER_CMD=""
# Look for server-like directories
for dir in cmd/*/; do
name=$(basename "$dir")
if echo "$name" | grep -qiE "server|api|web|app|service"; then
SERVER_CMD="go run ./$dir"
break
fi
done
# If no server-like dir, check if only one cmd/ exists
if [ -z "$SERVER_CMD" ]; then
cmd_count=$(ls -d cmd/*/ 2>/dev/null | wc -l)
if [ "$cmd_count" -eq 1 ]; then
SERVER_CMD="go run ./$(ls -d cmd/*/)"
fi
fi
fi
# Node.js: Check package.json
if test -f package.json; then
DEV_CMD=$(jq -r '.scripts.dev // empty' package.json 2>/dev/null)
START_CMD=$(jq -r '.scripts.start // empty' package.json 2>/dev/null)
# Detect package manager
test -f pnpm-lock.yaml && PKG_MGR="pnpm"
test -f yarn.lock && PKG_MGR="yarn"
test -f bun.lockb && PKG_MGR="bun"
PKG_MGR="${PKG_MGR:-npm}"
fi
# Python: Check for framework
if grep -q "FastAPI\|Flask" requirements.txt pyproject.toml 2>/dev/null; then
# Look for uvicorn/gunicorn patterns
grep -rn "uvicorn\|gunicorn" --include="*.py" . | head -1
fi
Step 3: Detect Service Dependencies
Scan these sources for service dependencies:
# Docker Compose (highest confidence)
if test -f docker-compose.yml; then
# Extract service names and images
grep -E "^\s+\w+:" docker-compose.yml | grep -v "version\|services"
grep "image:" docker-compose.yml
fi
# Code imports (medium confidence)
# PostgreSQL
grep -rq "pgx\|pq\|database/sql.*postgres\|psycopg\|pg.*Pool\|sequelize.*postgres\|TypeORM.*postgres" . 2>/dev/null && echo "postgres detected"
# MySQL
grep -rq "mysql\|mariadb" --include="*.go" --include="*.py" --include="*.ts" . 2>/dev/null && echo "mysql detected"
# Redis
grep -rq "go-redis\|redigo\|redis\|ioredis\|bull" --include="*.go" --include="*.py" --include="*.ts" . 2>/dev/null && echo "redis detected"
# MongoDB
grep -rq "mongo\|bson\|mongoose" --include="*.go" --include="*.py" --include="*.ts" . 2>/dev/null && echo "mongodb detected"
# Kafka/RabbitMQ
grep -rq "kafka\|sarama\|confluent" . 2>/dev/null && echo "kafka detected"
grep -rq "rabbitmq\|amqp" . 2>/dev/null && echo "rabbitmq detected"
Step 4: Detect Environment Variables
# Scan .env.example or .env.sample
if test -f .env.example; then
cat .env.example
elif test -f .env.sample; then
cat .env.sample
fi
# Scan code for env var references
# Go
grep -rn "os.Getenv\|os.LookupEnv\|viper.Get" --include="*.go" . 2>/dev/null | head -30
# Node.js
grep -rn "process.env\." --include="*.ts" --include="*.js" . 2>/dev/null | head -30
# Python
grep -rn "os.environ\|os.getenv\|settings\." --include="*.py" . 2>/dev/null | head -30
# Detect sensitive variables
grep -rEi "(PASSWORD|SECRET|KEY|TOKEN|CREDENTIAL|AUTH)" --include="*.go" --include="*.ts" --include="*.py" . 2>/dev/null | grep -i "getenv\|environ\|process\.env\|viper" | head -20
Interactive Collection Flow (Mixed Mode)
Decision Matrix: When to Ask vs When to Auto-Fill vs When to Write TODO
| Information | Detectable? | Critical? | Action |
|---|---|---|---|
| Startup command | Often yes | Yes | Auto-detect → if fail, ask with the platform user-confirmation tool immediately |
| Health endpoint | Sometimes | Yes | Auto-detect → if fail, ask with the platform user-confirmation tool |
| Port | Usually | No | Auto-detect → default 8080 |
| Database type | Often | Yes (if code uses DB) | Auto-detect → if fail, ask with the platform user-confirmation tool |
| DB connection URL | No | Yes | Mark requires_user_input, use ${DATABASE_URL} |
| Redis/cache | Sometimes | No | Auto-detect → write TODO if unclear |
| API keys | No | Depends | Mark requires_user_input, use ${VAR_NAME} |
| Log level | Yes (default) | No | Auto-fill with "info" |
| Feature flags | Sometimes | No | Write TODO placeholder |
User Confirmation Templates
In Codex, use request_user_input. On other platforms, use the equivalent user-confirmation
tool. If no confirmation tool is available, record assumptions and required follow-up in
environment.json.
Template 1: Startup Command (critical, must ask if not detected)
{
"question": "Unable to automatically detect how to start this project. How is the application started for development?",
"header": "Startup",
"options": [
{
"label": "Custom command",
"description": "I'll provide the specific command (e.g., 'go run ./cmd/server', 'npm run dev')"
},
{
"label": "Docker Compose",
"description": "The project uses docker-compose up to start everything"
},
{
"label": "Makefile target",
"description": "There's a Makefile with run/start/dev targets"
},
{
"label": "Not applicable",
"description": "This is a library/package — no startup command needed"
}
]
}
Template 2: Database Dependency (critical if DB usage detected in code)
{
"question": "Detected database usage in code ({detected_db_type}). Please confirm the database setup:",
"header": "Database",
"options": [
{
"label": "Docker container",
"description": "Use Docker to run {db_type} locally (recommended for development)"
},
{
"label": "Local installation",
"description": "Database is installed directly on this machine"
},
{
"label": "Remote/cloud",
"description": "Database is hosted remotely (staging/dev environment)"
},
{
"label": "SQLite/embedded",
"description": "Use an embedded database for development/testing"
}
]
}
Template 3: Sensitive Configuration (always ask if detected)
{
"question": "Detected sensitive environment variables in code: {var_list}. These are needed for the application to run. How should they be configured?",
"header": "Secrets",
"options": [
{
"label": "Environment variables (recommended)",
"description": "Reference via ${VAR_NAME} — you set them in your shell profile"
},
{
"label": "Safe test values available",
"description": "Some of these have safe test/development values that can be used"
},
{
"label": "Config file reference",
"description": "Reference a local config file like ~/.config/app/secrets.json"
},
{
"label": "Skip for now",
"description": "Mark as TODO — fill in later before running verification"
}
]
}
Startup Scripts Generation
setup-env.sh
#!/usr/bin/env bash
# Environment setup script — starts required services
# Generated by ecl-harness-engineer, consumed by harness-executor
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
echo "=== Setting up environment for {project_name} ==="
# Start services via Docker (if docker-compose exists)
if [ -f "$PROJECT_ROOT/docker-compose.yml" ]; then
echo "Starting services via docker-compose..."
docker-compose -f "$PROJECT_ROOT/docker-compose.yml" up -d
fi
# Or start individual services
# {auto-generated based on detected services}
# Wait for services to be ready
echo "Waiting for services..."
# {auto-generated health checks}
# Run migrations (if applicable)
# {auto-generated migration commands}
echo "=== Environment ready ==="
start-server.sh
#!/usr/bin/env bash
# Application startup script
# Generated by ecl-harness-engineer, consumed by harness-executor
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
cd "$PROJECT_ROOT"
# Set default environment variables
export PORT="${PORT:-8080}"
export ENV="${ENV:-development}"
export LOG_LEVEL="${LOG_LEVEL:-debug}"
# Start the application
echo "Starting {project_name} on port $PORT..."
{startup_command}
teardown-env.sh
#!/usr/bin/env bash
# Environment teardown script
# Generated by ecl-harness-engineer, consumed by harness-executor
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
echo "=== Tearing down environment ==="
# Stop Docker services
if [ -f "$PROJECT_ROOT/docker-compose.yml" ]; then
docker-compose -f "$PROJECT_ROOT/docker-compose.yml" down -v
fi
# Clean up optional runtime verification artifacts when advanced tracing is enabled
if [ -d "$PROJECT_ROOT/harness/trace" ]; then
rm -rf "$PROJECT_ROOT/harness/trace/verify-report.json" 2>/dev/null
rm -rf "$PROJECT_ROOT/harness/trace/verification-report.json" 2>/dev/null
fi
echo "=== Environment cleaned up ==="
Readiness Check Types
| Type | When to Use | Config |
|---|---|---|
http |
Web API with health endpoint | { "endpoint": "/health", "port": 8080, "expected_status": 200 } |
tcp |
Service that listens on a port but no HTTP | { "host": "localhost", "port": 5432 } |
log_pattern |
Service that logs a "ready" message | { "pattern": "Server listening on", "timeout_seconds": 30 } |
process |
Just check the process is running | { "command": "pgrep -f 'my-app'" } |
none |
Library or no startup needed | (omit readiness section) |
Sensitive Configuration Security
Core rule: never hardcode sensitive values in environment.json or scripts.
Safe Patterns
| Pattern | Syntax | Example |
|---|---|---|
| Environment variable | ${VAR_NAME} |
"password": "${DB_PASSWORD}" |
| With default | ${VAR_NAME:-default} |
"port": "${PORT:-8080}" |
| Config file ref | $file:path:key |
"key": "$file:~/.config/app/secrets.json:api.key" |
Detection and Marking
When sensitive variables are detected in code, mark them in _meta.requires_user_input:
{
"_meta": {
"requires_user_input": ["DATABASE_URL", "JWT_SECRET", "API_KEY"],
"todos": [
"Set DATABASE_URL environment variable before running verification",
"Configure JWT_SECRET for authentication testing"
]
}
}
What NOT to Put in environment.json
- Actual passwords, API keys, tokens
- Connection strings with embedded credentials
- Test credentials that might be valid
- Internal URLs that should not be in version control
Autonomous Mode (User Confirmation Tool Not Available)
When no user-confirmation tool is available:
- Auto-detect everything possible — use all detection strategies above
- Apply conservative defaults — use most common values
- Mark unknowns as TODO — never guess at critical config
- Document assumptions — explain what was detected and what's assumed
{
"_meta": {
"generated_by": "ecl-harness-engineer",
"mode": "autonomous",
"assumptions": [
"Startup command inferred from cmd/server/main.go",
"Port 8080 assumed (most common for Go web servers)",
"PostgreSQL detected from pgx import in internal/storage/"
],
"requires_user_input": ["DATABASE_URL"],
"todos": [
"Verify startup command is correct",
"Confirm PostgreSQL connection details",
"Set DATABASE_URL environment variable"
]
}
}