Spin up Fly.io machines to execute Claude Code prompts
Project description
flaude
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
- A Docker container with Claude Code, git, and gh CLI pre-installed boots on Fly.io
- The entrypoint clones your specified repos into
/workspace - Claude Code runs your prompt in print mode (
-p) - Logs stream back to your process via HTTP log drains (NDJSON)
- 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 default — pytest 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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7470d477944cddda16505d47159c2a2725a4c28fbc8ffc8c85b5f39c317c29c1
|
|
| MD5 |
a7f24b1a129b63d5a39e544b657c1082
|
|
| BLAKE2b-256 |
b3a6d53c810573b08f70478d44e2e9b103734bea8d930d3aa630d1b884998d23
|
Provenance
The following attestation bundles were made for flaude-0.2.0.tar.gz:
Publisher:
publish.yml on ravi-hq/flaude
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
flaude-0.2.0.tar.gz -
Subject digest:
7470d477944cddda16505d47159c2a2725a4c28fbc8ffc8c85b5f39c317c29c1 - Sigstore transparency entry: 1192007181
- Sigstore integration time:
-
Permalink:
ravi-hq/flaude@1c742ec92ce2199ba8985ce13c3db4baf10cf982 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/ravi-hq
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1c742ec92ce2199ba8985ce13c3db4baf10cf982 -
Trigger Event:
release
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
446db435d210c5d2a0575e41bac01a7d1f98c0d322059d142b4d8805b44ddb3e
|
|
| MD5 |
89490f38c1fdb7bea7319e34e8fcb2eb
|
|
| BLAKE2b-256 |
254d4d11e0cd1904632a4a8f5c1e4f50229cdfe9e4816b462eba7423fa3f2c8b
|
Provenance
The following attestation bundles were made for flaude-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on ravi-hq/flaude
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
flaude-0.2.0-py3-none-any.whl -
Subject digest:
446db435d210c5d2a0575e41bac01a7d1f98c0d322059d142b4d8805b44ddb3e - Sigstore transparency entry: 1192007195
- Sigstore integration time:
-
Permalink:
ravi-hq/flaude@1c742ec92ce2199ba8985ce13c3db4baf10cf982 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/ravi-hq
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1c742ec92ce2199ba8985ce13c3db4baf10cf982 -
Trigger Event:
release
-
Statement type: