Skip to content

Lifecycle Hooks

Run commands at specific stages of the container lifecycle — install dependencies, start background services, or set up the environment before Claude begins working.

Hook execution order

sequenceDiagram
    participant S as Stromboli
    participant C as Container
    participant Claude

    Note over S,C: First Run (New Session)
    S->>C: Start container
    S->>C: OnCreateCommand
    S->>C: PostCreate
    S->>C: PostStart
    S->>Claude: Run Claude

    Note over S,C: Subsequent Runs (Resume)
    S->>C: Start container
    S->>C: PostStart
    S->>Claude: Run Claude
Hook Runs when Use for
on_create_command First run only Installing dependencies, cloning repos
post_create First run only, after on_create Building projects, running migrations
post_start Every run (including resumes) Starting services, setting env vars

Basic usage

curl -X POST localhost:8080/run \
  -d '{
    "prompt": "Run the tests",
    "workdir": "/workspace",
    "podman": {
      "image": "python:3.12",
      "volumes": ["/home/user/myproject:/workspace"],
      "lifecycle": {
        "on_create_command": ["pip install -r requirements.txt"],
        "post_start": ["redis-server --daemonize yes"]
      }
    }
  }'

Examples

Python project

{
  "podman": {
    "image": "python:3.12",
    "lifecycle": {
      "on_create_command": [
        "pip install -r requirements.txt",
        "pip install -r requirements-dev.txt"
      ],
      "post_start": ["export PYTHONPATH=/workspace"]
    }
  }
}

Node.js with database

{
  "podman": {
    "image": "node:20",
    "lifecycle": {
      "on_create_command": ["npm ci"],
      "post_create": ["npm run build", "npm run db:migrate"],
      "post_start": ["npm run db:start &"],
      "hooks_timeout": "10m"
    }
  }
}

Go with Redis

{
  "podman": {
    "image": "golang:1.22",
    "lifecycle": {
      "on_create_command": [
        "apt-get update && apt-get install -y redis-server",
        "go mod download"
      ],
      "post_start": ["redis-server --daemonize yes"]
    }
  }
}

Timeout

Set a specific timeout for hooks so they don't eat into the main execution timeout:

{
  "podman": {
    "timeout": "30m",
    "lifecycle": {
      "hooks_timeout": "5m",
      "on_create_command": ["pip install -r requirements.txt"]
    }
  }
}

Fail-fast behavior

Hooks are chained with &&. If any command fails, subsequent hooks and Claude do not run.

graph LR
    A[OnCreateCommand] -->|success| B[PostCreate]
    B -->|success| C[PostStart]
    C -->|success| D[Claude]
    A -->|failure| E[Container Stops]
    B -->|failure| E
    C -->|failure| E

Session continuation

When you resume a session, only post_start hooks run. on_create_command and post_create are skipped since dependencies are already installed.

This means: - Put dependency installation in on_create_command (runs once) - Put service startup in post_start (runs every time)

Idempotency

Design hooks to be safe to re-run

Init hooks may run again if the first attempt crashes. Use idempotent commands like pip install and npm ci rather than commands that fail or produce side effects on re-run.

Security

Limit Value
Max args per command 100
Max arg length 4,096 chars
Max total hook size 65,536 chars
Max total args 200
Max timeout 1 hour

All hook arguments are shell-escaped to prevent injection.

Troubleshooting

Hook command failed — Check the command exists in the image. Test manually: podman run -it python:3.12 sh -c "your command"

Timeout exceeded — Increase hooks_timeout in the lifecycle config.

Service not available on resume — Move service startup to post_start instead of on_create_command.