Skip to content

Init Scripts Guide

Init scripts (init.sh and init.ps1) set up the development environment at the start of each agent session. They ensure a consistent, reproducible starting point.

Purpose

Init scripts solve several problems for long-running agent workflows:

  1. Reproducible startup - Same environment every session
  2. Stale process cleanup - Kill orphaned dev servers
  3. Health verification - Confirm the app is running
  4. Background execution - Don't block the agent

Key Design Principle

Init scripts must start servers in the background and exit cleanly. The agent needs to continue working, not wait for a server to terminate.

Default Behavior

The included templates perform these steps:

graph LR
    A[Check Prerequisites] --> B[Kill Stale Processes]
    B --> C[Install Dependencies]
    C --> D[Start Dev Server]
    D --> E[Wait for Ready]
    E --> F[Health Check]
    F --> G[Exit Successfully]

Configuration

Both scripts have a configuration section at the top:

# Configuration - adjust these for your project
DEV_PORT=3000  # Change to match your dev server port
# Configuration - adjust these for your project
$DEV_PORT = 3000  # Change to match your dev server port

Customization by Stack

Node.js (Default)

The templates come pre-configured for Node.js projects:

# Check for Node.js
if ! command -v node &> /dev/null; then
    echo "❌ Node.js is required but not installed"
    exit 1
fi

# Install dependencies
if [ ! -d "node_modules" ]; then
    npm install
fi

# Start dev server in background
npm run dev > .dev-server.log 2>&1 &
DEV_PID=$!
echo $DEV_PID > .dev-server.pid
# Check for Node.js
if (-not (Get-Command node -ErrorAction SilentlyContinue)) {
    Write-Host "❌ Node.js is required" -ForegroundColor Red
    exit 1
}

# Install dependencies
if (-not (Test-Path "node_modules")) {
    npm install
}

# Start dev server in background
$devJob = Start-Job -ScriptBlock {
    Set-Location $using:PWD
    npm run dev 2>&1
}

Python with uv

# Configuration
DEV_PORT=8000

# Check for uv
if ! command -v uv &> /dev/null; then
    echo "❌ uv is required but not installed"
    exit 1
fi
echo "   ✅ uv $(uv --version)"

# Sync dependencies
uv sync

# Start dev server (Django example)
uv run python manage.py runserver 0.0.0.0:$DEV_PORT > .dev-server.log 2>&1 &
DEV_PID=$!
echo $DEV_PID > .dev-server.pid
# Configuration
$DEV_PORT = 8000

# Check for uv
if (-not (Get-Command uv -ErrorAction SilentlyContinue)) {
    Write-Host "❌ uv is required" -ForegroundColor Red
    exit 1
}

# Sync dependencies
uv sync

# Start dev server (Django example)
$devJob = Start-Job -ScriptBlock {
    Set-Location $using:PWD
    uv run python manage.py runserver 0.0.0.0:$using:DEV_PORT 2>&1
}

Python with pip

# Configuration
DEV_PORT=5000

# Check for Python
if ! command -v python3 &> /dev/null; then
    echo "❌ Python 3 is required"
    exit 1
fi

# Create/activate virtual environment
if [ ! -d ".venv" ]; then
    python3 -m venv .venv
fi
source .venv/bin/activate

# Install dependencies
pip install -r requirements.txt

# Start Flask dev server
flask run --port $DEV_PORT > .dev-server.log 2>&1 &
DEV_PID=$!

Rust

# Configuration
DEV_PORT=8080

# Check for Cargo
if ! command -v cargo &> /dev/null; then
    echo "❌ Rust/Cargo is required"
    exit 1
fi

# Build in release mode
cargo build --release

# Start server
./target/release/myapp > .dev-server.log 2>&1 &
DEV_PID=$!

Go

# Configuration
DEV_PORT=8080

# Check for Go
if ! command -v go &> /dev/null; then
    echo "❌ Go is required"
    exit 1
fi

# Build and run
go build -o ./bin/server ./cmd/server
./bin/server > .dev-server.log 2>&1 &
DEV_PID=$!

Static Sites (MkDocs, Hugo, etc.)

# Configuration
DEV_PORT=8000

# Check for uv
if ! command -v uv &> /dev/null; then
    echo "❌ uv is required"
    exit 1
fi

# Sync and serve
uv sync
uv run mkdocs serve --dev-addr localhost:$DEV_PORT > .dev-server.log 2>&1 &
DEV_PID=$!
# Configuration
DEV_PORT=1313

hugo server --port $DEV_PORT > .dev-server.log 2>&1 &
DEV_PID=$!

Health Checks

The templates include health check logic with configurable timeouts:

# Wait for server with timeout
MAX_ATTEMPTS=30
ATTEMPT=0
SERVER_READY=false

while [ $ATTEMPT -lt $MAX_ATTEMPTS ] && [ "$SERVER_READY" = false ]; do
    sleep 1
    ATTEMPT=$((ATTEMPT + 1))
    if curl -s -o /dev/null "http://localhost:$DEV_PORT" 2>/dev/null; then
        SERVER_READY=true
    fi
done

if [ "$SERVER_READY" = false ]; then
    echo "❌ Server failed to start!"
    exit 1
fi
# Wait for server with timeout
$maxAttempts = 30
$attempt = 0
$serverReady = $false

while ($attempt -lt $maxAttempts -and -not $serverReady) {
    Start-Sleep -Seconds 1
    $attempt++
    $connection = Test-NetConnection -ComputerName localhost -Port $DEV_PORT -WarningAction SilentlyContinue
    if ($connection.TcpTestSucceeded) {
        $serverReady = $true
    }
}

if (-not $serverReady) {
    Write-Host "❌ Server failed to start!" -ForegroundColor Red
    exit 1
}

Custom Health Endpoints

For APIs, check a specific health endpoint:

# Instead of just checking the port, verify the health endpoint
HTTP_CODE=$(curl -s -o /dev/null -w '%{http_code}' "http://localhost:$DEV_PORT/health")
if [ "$HTTP_CODE" != "200" ]; then
    echo "❌ Health check failed (HTTP $HTTP_CODE)"
    exit 1
fi

Process Management

Tracking the PID

The scripts save the server PID for later cleanup:

# Save PID to file
echo $DEV_PID > .dev-server.pid
echo "Started dev server (PID: $DEV_PID)"

Stopping the Server

To stop the dev server:

kill $(cat .dev-server.pid) 2>/dev/null
rm .dev-server.pid
Stop-Job -Id (Get-Content .dev-job-id.txt)
Remove-Item .dev-job-id.txt

Viewing Logs

Server output is captured to .dev-server.log:

# View live output
tail -f .dev-server.log

# View last 50 lines
tail -n 50 .dev-server.log

Best Practices

Do

  • ✅ Start servers in background (use & or Start-Job)
  • ✅ Exit the script after server is ready
  • ✅ Include health checks with timeouts
  • ✅ Save PID for cleanup
  • ✅ Capture output to log files
  • ✅ Kill stale processes before starting new ones

Don't

  • ❌ Block waiting for server to terminate
  • ❌ Skip health checks
  • ❌ Hardcode paths (use relative paths)
  • ❌ Ignore exit codes
  • ❌ Leave orphaned processes running

Integration with Klondike

Init scripts are called during the session start workflow:

# Standard session start
klondike session start --focus "F001 - Feature description"

# Then run init script
./init.sh  # or .\init.ps1 on Windows

The session-start prompt includes init script execution as a standard step.

Troubleshooting

Server Won't Start

  1. Check .dev-server.log for error messages
  2. Verify the port isn't in use: lsof -i :3000
  3. Ensure dependencies are installed

Health Check Fails

  1. Increase MAX_ATTEMPTS for slow-starting apps
  2. Check if the server uses a different startup path
  3. Verify firewall isn't blocking localhost

Stale Processes

If the script can't kill stale processes:

# Manually find and kill
lsof -ti:3000 | xargs kill -9

# Or on Windows
Get-Process | Where-Object { $_.MainWindowTitle -match "node" } | Stop-Process

Example: Complete Custom Script

Here's a complete example for a Python/FastAPI project with uv:

#!/bin/bash
set -e

# Configuration
DEV_PORT=8000
HEALTH_ENDPOINT="/api/health"

echo "🚀 Starting FastAPI development environment..."

# Prerequisites
if ! command -v uv &> /dev/null; then
    echo "❌ uv is required"
    exit 1
fi
echo "   ✅ uv $(uv --version)"

# Clean up stale processes
STALE_PID=$(lsof -ti:$DEV_PORT 2>/dev/null || true)
if [ -n "$STALE_PID" ]; then
    kill -9 $STALE_PID 2>/dev/null || true
    sleep 1
fi

# Install/sync dependencies
uv sync

# Start uvicorn in background
uv run uvicorn app.main:app --host 0.0.0.0 --port $DEV_PORT --reload > .dev-server.log 2>&1 &
echo $! > .dev-server.pid

# Wait for health endpoint
MAX_ATTEMPTS=30
for i in $(seq 1 $MAX_ATTEMPTS); do
    if curl -sf "http://localhost:$DEV_PORT$HEALTH_ENDPOINT" > /dev/null 2>&1; then
        echo "✅ Server ready at http://localhost:$DEV_PORT"
        echo "📖 API docs: http://localhost:$DEV_PORT/docs"
        exit 0
    fi
    sleep 1
done

echo "❌ Server failed to start"
cat .dev-server.log
exit 1

Next Steps