Claude-code-plugins-plus-skills figma-reliability-patterns
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/figma-pack/skills/figma-reliability-patterns" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-skills-figma-reliability-patterns && rm -rf "$T"
manifest:
plugins/saas-packs/figma-pack/skills/figma-reliability-patterns/SKILL.mdsource content
Figma Reliability Patterns
Overview
Production reliability patterns for Figma REST API integrations. Figma is an external dependency -- your application must handle its outages, rate limits, and slow responses without cascading failures.
Prerequisites
- Working Figma API integration
- Understanding of circuit breaker pattern
- Cache or file system for fallback data
Instructions
Step 1: Circuit Breaker
// Prevent cascading failures when Figma is down class FigmaCircuitBreaker { private failures = 0; private lastFailure = 0; private state: 'closed' | 'open' | 'half-open' = 'closed'; constructor( private threshold = 5, // Open after 5 failures private resetTimeMs = 30_000 // Try again after 30s ) {} async execute<T>(fn: () => Promise<T>): Promise<T> { if (this.state === 'open') { if (Date.now() - this.lastFailure > this.resetTimeMs) { this.state = 'half-open'; console.log('[figma-circuit] State: half-open (testing recovery)'); } else { throw new Error('Figma circuit breaker is OPEN -- failing fast'); } } try { const result = await fn(); if (this.state === 'half-open') { this.state = 'closed'; this.failures = 0; console.log('[figma-circuit] State: closed (recovered)'); } return result; } catch (error) { this.failures++; this.lastFailure = Date.now(); if (this.failures >= this.threshold) { this.state = 'open'; console.warn(`[figma-circuit] State: OPEN after ${this.failures} failures`); } throw error; } } getState() { return this.state; } } const figmaBreaker = new FigmaCircuitBreaker(); // Usage async function safeFigmaCall<T>(fn: () => Promise<T>): Promise<T> { return figmaBreaker.execute(fn); }
Step 2: Cached Fallback
import { readFileSync, writeFileSync, existsSync } from 'fs'; // Serve cached data when Figma is unavailable class FigmaFallbackCache { constructor(private cacheDir = '.figma-cache') {} private getPath(key: string) { return `${this.cacheDir}/${key.replace(/[^a-zA-Z0-9]/g, '_')}.json`; } save(key: string, data: any) { const { mkdirSync } = require('fs'); mkdirSync(this.cacheDir, { recursive: true }); writeFileSync(this.getPath(key), JSON.stringify({ data, cachedAt: new Date().toISOString(), })); } load(key: string): { data: any; cachedAt: string } | null { const path = this.getPath(key); if (!existsSync(path)) return null; return JSON.parse(readFileSync(path, 'utf-8')); } } const fallbackCache = new FigmaFallbackCache(); async function fetchWithFallback<T>( cacheKey: string, fetcher: () => Promise<T> ): Promise<{ data: T; fromCache: boolean; cachedAt?: string }> { try { const data = await safeFigmaCall(fetcher); // Update cache with fresh data fallbackCache.save(cacheKey, data); return { data, fromCache: false }; } catch (error) { console.warn(`Figma unavailable, loading cached ${cacheKey}`); const cached = fallbackCache.load(cacheKey); if (cached) { return { data: cached.data as T, fromCache: true, cachedAt: cached.cachedAt }; } throw new Error(`Figma unavailable and no cached data for ${cacheKey}`); } }
Step 3: Retry with Backoff (Respecting Retry-After)
async function figmaRetry<T>( fn: () => Promise<Response>, maxRetries = 3 ): 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') || '60'); if (attempt < maxRetries) { console.warn(`429 -- waiting ${retryAfter}s (attempt ${attempt + 1}/${maxRetries})`); await new Promise(r => setTimeout(r, retryAfter * 1000)); continue; } } if (res.status >= 500 && attempt < maxRetries) { const delay = Math.min(1000 * Math.pow(2, attempt), 30_000); const jitter = Math.random() * 1000; await new Promise(r => setTimeout(r, delay + jitter)); continue; } throw new FigmaApiError(res.status, await res.text()); } throw new Error('Max retries exceeded'); }
Step 4: Request Timeout
// Prevent requests from hanging indefinitely async function figmaFetchWithTimeout( path: string, token: string, timeoutMs = 15_000 ): Promise<Response> { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), timeoutMs); try { return await fetch(`https://api.figma.com${path}`, { headers: { 'X-Figma-Token': token }, signal: controller.signal, }); } catch (error) { if (error instanceof Error && error.name === 'AbortError') { throw new Error(`Figma request timed out after ${timeoutMs}ms: ${path}`); } throw error; } finally { clearTimeout(timeout); } }
Step 5: Health-Aware Request Routing
// Only make non-critical Figma calls when the API is healthy class FigmaHealthTracker { private healthy = true; private lastCheck = 0; private checkIntervalMs = 30_000; async isHealthy(token: string): Promise<boolean> { if (Date.now() - this.lastCheck < this.checkIntervalMs) { return this.healthy; } try { const res = await figmaFetchWithTimeout('/v1/me', token, 5000); this.healthy = res.ok; } catch { this.healthy = false; } this.lastCheck = Date.now(); return this.healthy; } } const healthTracker = new FigmaHealthTracker(); async function conditionalFigmaCall<T>( token: string, critical: boolean, fn: () => Promise<T>, fallback: () => Promise<T> ): Promise<T> { const healthy = await healthTracker.isHealthy(token); if (!healthy && !critical) { console.log('Figma unhealthy, using fallback for non-critical call'); return fallback(); } return fetchWithFallback('default', fn).then(r => r.data); }
Output
- Circuit breaker preventing cascading failures
- Cached fallback serving stale data during outages
- Retry logic respecting Figma's
headerRetry-After - Request timeouts preventing hung connections
- Health-aware routing for non-critical calls
Error Handling
| Issue | Cause | Solution |
|---|---|---|
| Circuit stays open | Threshold too low | Increase threshold or decrease reset time |
| Stale fallback data | Cache not refreshed | Refresh cache on successful calls |
| Retry loops | Not respecting Retry-After | Always use the header value |
| Timeout too short | Large file responses | Increase timeout for calls |
Resources
Next Steps
For policy enforcement, see
figma-policy-guardrails.