astral-sh/ruff · error · Error

Failed to save playground: ${response.status}

Error message

Failed to save playground: ${response.status}

What it means

Thrown by the Ruff playground's savePlayground when the POST to the share API returns a non-2xx status. The request reached the Cloudflare worker (https://api.astral-1ad.workers.dev in prod, http://localhost:8787 in dev) but it refused or failed to persist the snippet.

Source

Thrown at playground/ruff/src/Editor/api.ts:30

 */
export async function fetchPlayground(id: string): Promise<Playground | null> {
  const response = await fetch(`${API_URL}/${encodeURIComponent(id)}`);
  if (!response.ok) {
    throw new Error(`Failed to fetch playground ${id}: ${response.status}`);
  }
  return await response.json();
}

/**
 * Save a playground and return its ID.
 */
export async function savePlayground(playground: Playground): Promise<string> {
  const response = await fetch(API_URL, {
    method: "POST",
    body: JSON.stringify(playground),
  });
  if (!response.ok) {
    throw new Error(`Failed to save playground: ${response.status}`);
  }
  return await response.text();
}

View on GitHub (pinned to 672bb4edf0)

Solutions

  1. Check response.status in browser devtools to distinguish 4xx (payload problem) from 5xx (server problem)
  2. Retry after a short delay; transient worker/storage errors usually clear
  3. In local dev, confirm the worker on localhost:8787 is running the current share API and reload
  4. If 5xx persists, reduce the snippet size or report the outage; keep a local copy of the source

Example fix

// before
const id = await savePlayground(playground);

// after
let id: string | null = null;
for (let attempt = 0; attempt < 2 && !id; attempt++) {
  try {
    id = await savePlayground(playground);
  } catch (error) {
    if (attempt === 1) console.error((error as Error).message);
  }
}
Defensive patterns

Strategy: try-catch

Validate before calling

const payload = JSON.stringify(playground);
if (payload.length > 1_000_000) {
  throw new RangeError('Playground payload too large to save');
}

Type guard

function isSavePayload(p: Playground): boolean {
  return p.pythonSource.length > 0 && p.settingsSource.length >= 0;
}

Try / catch

try {
  return await savePlayground(playground);
} catch (error) {
  console.error(`Save failed: ${(error as Error).message}`);
  return null; // caller keeps local state and may retry
}

Prevention

When it happens

Trigger: POST to API_URL with a JSON body returning 5xx (worker failure, storage write error) or a 4xx (payload rejected by the route).

Common situations: Sharing from the playground during a worker outage or deployment window; oversized settings/source payload; local dev where the worker is answering but not the share route.

Related errors


AI-assisted analysis of astral-sh/ruff@672bb4edf0 (2026-08-16). Data as JSON: /api/errors/4d8b0287c8132739. Report an issue: GitHub.