Claude-code-plugins-plus-skills flyio-rate-limits

install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/flyio-pack/skills/flyio-rate-limits" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-skills-flyio-rate-limits && rm -rf "$T"
manifest: plugins/saas-packs/flyio-pack/skills/flyio-rate-limits/SKILL.md
source content

Fly.io Rate Limits

Overview

The Fly.io Machines API rate-limits per organization, with write operations (create, delete, update) throttled much more aggressively than reads. Deploying fleets of edge machines across multiple regions can easily trigger 429s, especially during rolling deployments or auto-scaling events. The API returns a

Retry-After
header on rate-limited responses, and organizations running 50+ machines should implement client-side token bucket limiting to avoid cascading failures during high-churn operations.

Rate Limit Reference

EndpointLimitWindowScope
Machine create/delete10 req1 minutePer org
Machine start/stop30 req1 minutePer org
Machine list/get120 req1 minutePer org
App create/delete5 req1 minutePer org
Volume operations15 req1 minutePer org

Rate Limiter Implementation

class FlyRateLimiter {
  private tokens: number;
  private lastRefill: number;
  private readonly max: number;
  private readonly refillRate: number;
  private queue: Array<{ resolve: () => void }> = [];

  constructor(maxPerMinute: number) {
    this.max = maxPerMinute;
    this.tokens = maxPerMinute;
    this.lastRefill = Date.now();
    this.refillRate = maxPerMinute / 60_000;
  }

  async acquire(): Promise<void> {
    this.refill();
    if (this.tokens >= 1) { this.tokens -= 1; return; }
    return new Promise(resolve => this.queue.push({ resolve }));
  }

  private refill() {
    const now = Date.now();
    this.tokens = Math.min(this.max, this.tokens + (now - this.lastRefill) * this.refillRate);
    this.lastRefill = now;
    while (this.tokens >= 1 && this.queue.length) {
      this.tokens -= 1;
      this.queue.shift()!.resolve();
    }
  }
}

const writeLimiter = new FlyRateLimiter(8);  // leave headroom under 10/min
const readLimiter = new FlyRateLimiter(100);

Retry Strategy

async function flyRetry<T>(fn: () => Promise<Response>, maxRetries = 4): Promise<T> {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const res = await fn();
    if (res.ok) return res.json();
    if (res.status === 429) {
      const retryAfter = parseInt(res.headers.get("Retry-After") || "10", 10);
      const delay = retryAfter * 1000 + Math.random() * 2000;
      await new Promise(r => setTimeout(r, delay));
      continue;
    }
    if (res.status >= 500 && attempt < maxRetries) {
      await new Promise(r => setTimeout(r, Math.pow(2, attempt) * 1000));
      continue;
    }
    throw new Error(`Fly API ${res.status}: ${await res.text()}`);
  }
  throw new Error("Max retries exceeded");
}

Batch Processing

async function rollingDeployMachines(appId: string, configs: any[], batchSize = 3) {
  const results: any[] = [];
  for (let i = 0; i < configs.length; i += batchSize) {
    const batch = configs.slice(i, i + batchSize);
    const batchResults = await Promise.all(
      batch.map(async cfg => {
        await writeLimiter.acquire();
        return flyRetry(() =>
          fetch(`https://api.machines.dev/v1/apps/${appId}/machines`, {
            method: "POST", headers, body: JSON.stringify(cfg),
          })
        );
      })
    );
    results.push(...batchResults);
    if (i + batchSize < configs.length) await new Promise(r => setTimeout(r, 10_000));
  }
  return results;
}

Error Handling

IssueCauseFix
429 on machine createExceeded 10 writes/min org limitUse Retry-After header, batch deploys
429 on fleet listMonitoring polling too fastCache responses, poll every 30s max
Timeout on volume attachVolume in another regionVerify region match before attach
503 during region outageSpecific edge region downFail over to secondary region
409 on machine updateConcurrent config changeRe-fetch machine state, retry with latest version

Resources

Next Steps

See

flyio-performance-tuning
.