Skip to main content

Spin up Fly.io machines to execute Claude Code prompts

Project description

flaude

CI PyPI Python License: MIT Docs

On-demand Claude Code execution on Fly.io machines.

Spin up ephemeral VMs, run Claude Code prompts against your repos, stream the output back, and auto-destroy the machines when done. No persistent infrastructure required.

How it works

Your code                    Fly.io
───────                      ──────
MachineConfig ──► create VM ──► clone repos
                                  │
                              run Claude Code
                                  │
            ◄── stream logs ◄─ stdout/stderr
                                  │
              destroy VM ◄──── exit
  1. A Docker container with Claude Code, git, and gh CLI pre-installed boots on Fly.io
  2. The entrypoint clones your specified repos into /workspace
  3. Claude Code runs your prompt in print mode (-p)
  4. Logs stream back to your process via HTTP log drains (NDJSON)
  5. The machine is always destroyed after completion (guaranteed via try/finally)

Install

pip install flaude

Requires Python 3.11+. The only runtime dependency is httpx.

Prerequisites

  • Fly.io account with a valid FLY_API_TOKEN
  • Claude Code OAuth token for authenticating Claude Code on the machine
  • GitHub credentials (username + PAT) if cloning private repos
  • Docker (only needed if building/pushing the container image yourself)

Quick start

Run a prompt and wait for the result

import asyncio
from flaude import MachineConfig, ensure_app, run_and_destroy

async def main():
    app = await ensure_app("my-flaude-app")

    config = MachineConfig(
        claude_code_oauth_token="sk-ant-oat-...",
        github_username="you",
        github_token="ghp_...",
        prompt="Find and fix any type errors in src/",
        repos=["https://github.com/you/your-repo"],
    )

    result = await run_and_destroy(app.name, config)
    print(f"Exit code: {result.exit_code}")

asyncio.run(main())

Stream logs in real time

from flaude import MachineConfig, run_with_logs

async def main():
    config = MachineConfig(
        claude_code_oauth_token="sk-ant-oat-...",
        prompt="Refactor the auth module to use JWT",
        repos=["https://github.com/you/your-repo"],
    )

    async with await run_with_logs("my-flaude-app", config) as stream:
        async for line in stream:
            print(line)

    result = await stream.result()
    print(f"Done: exit={result.exit_code}")

Run multiple prompts concurrently

from flaude import ConcurrentExecutor, ExecutionRequest, MachineConfig

async def main():
    executor = ConcurrentExecutor("my-flaude-app", max_concurrency=3)

    requests = [
        ExecutionRequest(
            config=MachineConfig(prompt="Add tests for auth", ...),
            tag="auth-tests",
        ),
        ExecutionRequest(
            config=MachineConfig(prompt="Add tests for billing", ...),
            tag="billing-tests",
        ),
    ]

    batch = await executor.run_batch(requests)
    print(f"{batch.succeeded}/{batch.total} succeeded")

API overview

Configuration

Class Purpose
MachineConfig Full machine configuration: prompt, repos, credentials, VM size, region
RepoSpec Repository spec with optional branch and target directory

Execution

Function / Class Purpose
run_and_destroy() Run prompt, wait for exit, destroy machine. Raises on failure.
run() Same as above but doesn't raise on non-zero exit.
run_with_logs() Run with real-time log streaming via async iterator.
ConcurrentExecutor Run multiple prompts in parallel with optional concurrency limits.

App & machine management

Function Purpose
ensure_app() Get or create a Fly.io app
create_app() / get_app() Explicit app create/get
create_machine() Create a Fly machine from config
stop_machine() / destroy_machine() Machine lifecycle control

Log infrastructure

Class / Function Purpose
LogDrainServer HTTP server that receives Fly.io log drain POSTs
LogCollector Routes log lines to per-machine async queues
LogStream Async iterator over a machine's log output with timeout support
StreamingRun Combined async iterator + context manager for streaming executions
fetch_machine_logs() Fetch historical logs from Fly platform API (works after machine exits)

Image management

Function Purpose
ensure_image() Build and push the Docker image if needed
docker_build() / docker_push() Explicit build/push

Results & errors

Class Purpose
RunResult Exit code, final state, and machine ID
MachineExitError Raised on non-zero exit; includes captured log tail
BatchResult Aggregated results from concurrent execution
ExecutionResult Per-request result within a batch

Configuration reference

MachineConfig fields:

Field Default Description
image ghcr.io/ravi-hq/flaude:latest Docker image
claude_code_oauth_token (required) Claude Code auth token
github_username "" GitHub username for private repos
github_token "" GitHub PAT for private repos
prompt (required) The Claude Code prompt to execute
repos [] Repos to clone (URLs or RepoSpec objects)
region "iad" Fly.io region
vm_size "performance-2x" VM preset
vm_cpus 2 vCPUs
vm_memory_mb 4096 RAM in MB
auto_destroy True Auto-destroy on exit
env {} Additional environment variables
metadata {} Machine metadata key-value pairs

Environment variables

Set in your local environment:

Variable Purpose
FLY_API_TOKEN Authenticate with the Fly.io Machines API

Set automatically on the machine by flaude:

Variable Purpose
CLAUDE_CODE_OAUTH_TOKEN Claude Code authentication
GITHUB_USERNAME Git credential for repo cloning
GITHUB_TOKEN Git credential for repo cloning
FLAUDE_REPOS JSON array of repo specs
FLAUDE_PROMPT The prompt string

Development

git clone https://github.com/ravi-hq/flaude.git
cd flaude
uv sync --extra dev      # install all dev dependencies
make test                # run unit tests
make check               # lint + type check + security scan

See CONTRIBUTING.md for the full contribution guide.

E2E validation tests

E2E tests spin up real Fly.io machines, run Claude Code, and verify the full lifecycle. They are excluded by defaultpytest alone never runs them.

Prerequisites

All required tokens are in .env:

Token Purpose
FLY_API_TOKEN Authenticates Fly.io API calls from your machine
CLAUDE_CODE_OAUTH_TOKEN Forwarded into the Fly machine for Claude Code auth
GITHUB_USERNAME Git clone auth (optional, for private repo tests)
GITHUB_TOKEN Git clone auth (optional, for private repo tests)

Optional:

Env var Purpose
FLAUDE_E2E_PRIVATE_REPO Full URL of a private repo to test cloning

The Docker image ghcr.io/ravi-hq/flaude:latest must be pushed before running E2E tests:

source .env && python -c "
import asyncio
from flaude import ensure_image
asyncio.run(ensure_image('flaude'))
"

The image is built for linux/amd64 (required by Fly.io) regardless of your host architecture.

Running E2E tests

source .env && pytest -m e2e -v

That's it. Each test creates a real Fly machine, runs a prompt, checks the output, and destroys the machine. Expect ~1-3 minutes per test.

What the tests validate

Test What it proves
test_smoke_run_and_destroy Full lifecycle works: create machine → run prompt → exit 0 → destroy
test_machine_logs Fetches logs via Fly platform API; verifies [flaude:exit:0] marker
test_public_repo_clone Public GitHub repo clones successfully before Claude Code runs
test_private_repo_clone Private repo clone with credentials (skipped if creds absent)
test_machine_cleanup_on_success Machine is actually destroyed after run (404 on get)

Running specific tests

# Just the smoke test (fastest, ~1 min):
source .env && pytest -m e2e -v -k smoke

# Everything including unit tests:
source .env && pytest -m "" -v

License

See repository for license details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

flaude-0.2.0.tar.gz (200.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

flaude-0.2.0-py3-none-any.whl (36.1 kB view details)

Uploaded Python 3

File details

Details for the file flaude-0.2.0.tar.gz.

File metadata

  • Download URL: flaude-0.2.0.tar.gz
  • Upload date:
  • Size: 200.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for flaude-0.2.0.tar.gz
Algorithm Hash digest
SHA256 7470d477944cddda16505d47159c2a2725a4c28fbc8ffc8c85b5f39c317c29c1
MD5 a7f24b1a129b63d5a39e544b657c1082
BLAKE2b-256 b3a6d53c810573b08f70478d44e2e9b103734bea8d930d3aa630d1b884998d23

See more details on using hashes here.

Provenance

The following attestation bundles were made for flaude-0.2.0.tar.gz:

Publisher: publish.yml on ravi-hq/flaude

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file flaude-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: flaude-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 36.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for flaude-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 446db435d210c5d2a0575e41bac01a7d1f98c0d322059d142b4d8805b44ddb3e
MD5 89490f38c1fdb7bea7319e34e8fcb2eb
BLAKE2b-256 254d4d11e0cd1904632a4a8f5c1e4f50229cdfe9e4816b462eba7423fa3f2c8b

See more details on using hashes here.

Provenance

The following attestation bundles were made for flaude-0.2.0-py3-none-any.whl:

Publisher: publish.yml on ravi-hq/flaude

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page