or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

cache-operations.mdconfiguration.mderror-handling.mdindex.md
tile.json

error-handling.mddocs/

Error Handling

Custom error types for validation failures and cache reservation conflicts, with patterns for graceful error handling in GitHub Actions workflows.

Capabilities

ValidationError

Error thrown for validation failures such as invalid cache keys, empty paths, or exceeded limits.

/**
 * Error thrown for validation failures (key size, path validation, etc.)
 */
class ValidationError extends Error {
  constructor(message: string);
  readonly name: 'ValidationError';
}

Common ValidationError scenarios:

  • Empty paths: When no paths are provided to cache operations
  • Invalid key format: Keys containing commas or exceeding 512 characters
  • Too many keys: More than 10 keys total (primary + restore keys)
  • Missing paths: When specified paths don't exist during save operations

Usage Examples:

import { restoreCache, saveCache, ValidationError } from '@actions/cache';

try {
  // This will throw ValidationError: empty paths
  await restoreCache([], 'my-key');
} catch (error) {
  if (error instanceof ValidationError) {
    console.error('Validation failed:', error.message);
    process.exit(1); // Stop workflow on validation errors
  }
}

try {
  // This will throw ValidationError: key too long
  const longKey = 'x'.repeat(600);
  await saveCache(['node_modules'], longKey);
} catch (error) {
  if (error instanceof ValidationError) {
    console.error('Invalid cache key:', error.message);
    // Fix the key and retry
  }
}

try {
  // This will throw ValidationError: too many keys
  const primaryKey = 'main-key';
  const restoreKeys = new Array(15).fill(0).map((_, i) => `key-${i}`);
  await restoreCache(['dist'], primaryKey, restoreKeys);
} catch (error) {
  if (error instanceof ValidationError) {
    console.error('Too many cache keys:', error.message);
    // Reduce the number of restore keys
  }
}

ReserveCacheError

Error thrown when unable to reserve cache, typically when another job is creating the same cache simultaneously.

/**
 * Error thrown when unable to reserve cache (e.g., another job creating same cache)
 */
class ReserveCacheError extends Error {
  constructor(message: string);
  readonly name: 'ReserveCacheError';
}

Common ReserveCacheError scenarios:

  • Concurrent cache creation: Multiple workflow jobs trying to create the same cache key
  • Cache service unavailable: Temporary service issues during cache reservation
  • Quota exceeded: Repository cache size limit reached (10GB)

Usage Examples:

import { saveCache, ReserveCacheError } from '@actions/cache';

try {
  const cacheId = await saveCache(['node_modules'], 'npm-deps-abc123');
  console.log(`Cache saved with ID: ${cacheId}`);
} catch (error) {
  if (error instanceof ReserveCacheError) {
    console.warn('Cache reservation failed:', error.message);
    console.warn('Another job may be creating this cache, continuing without caching');
    // Continue workflow - this is not a fatal error
  } else {
    throw error; // Re-throw other errors
  }
}

// Retry logic for ReserveCacheError
async function saveCacheWithRetry(paths: string[], key: string, maxRetries = 3): Promise<number | null> {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await saveCache(paths, key);
    } catch (error) {
      if (error instanceof ReserveCacheError && attempt < maxRetries) {
        console.warn(`Cache reservation attempt ${attempt} failed, retrying...`);
        await new Promise(resolve => setTimeout(resolve, 1000 * attempt)); // Exponential backoff
        continue;
      }
      if (error instanceof ReserveCacheError) {
        console.warn('Failed to reserve cache after all retries');
        return null; // Give up gracefully
      }
      throw error; // Re-throw non-reservation errors
    }
  }
  return null;
}

General Error Patterns

Cache operations are designed to be non-blocking for workflow execution. Server errors and network issues are typically logged as warnings rather than failing the entire workflow.

// Error types that may be encountered
interface HttpClientError extends Error {
  statusCode?: number;
}

Error Handling Patterns:

import { restoreCache, saveCache, ValidationError, ReserveCacheError } from '@actions/cache';
import { HttpClientError } from '@actions/http-client';

async function robustCacheRestore(
  paths: string[], 
  primaryKey: string, 
  restoreKeys?: string[]
): Promise<string | undefined> {
  try {
    return await restoreCache(paths, primaryKey, restoreKeys);
  } catch (error) {
    if (error instanceof ValidationError) {
      // Validation errors should fail the workflow
      console.error('Cache restore validation failed:', error.message);
      throw error;
    } else if (error instanceof HttpClientError && error.statusCode && error.statusCode >= 500) {
      // Server errors are logged as errors but don't fail the workflow
      console.error('Cache service error:', error.message);
      return undefined;
    } else {
      // Other errors are warnings
      console.warn('Cache restore failed:', error.message);
      return undefined;
    }
  }
}

async function robustCacheSave(
  paths: string[], 
  key: string
): Promise<number | null> {
  try {
    return await saveCache(paths, key);
  } catch (error) {
    if (error instanceof ValidationError) {
      // Validation errors should fail the workflow
      console.error('Cache save validation failed:', error.message);
      throw error;
    } else if (error instanceof ReserveCacheError) {
      // Reservation errors are informational
      console.info('Cache reservation failed (likely concurrent job):', error.message);
      return null;
    } else if (error instanceof HttpClientError && error.statusCode && error.statusCode >= 500) {
      // Server errors are logged as errors but don't fail the workflow
      console.error('Cache service error:', error.message);
      return null;
    } else {
      // Other errors are warnings
      console.warn('Cache save failed:', error.message);
      return null;
    }
  }
}

Complete Workflow Example

Here's a complete example showing proper error handling in a GitHub Actions workflow:

import { 
  restoreCache, 
  saveCache, 
  isFeatureAvailable,
  ValidationError, 
  ReserveCacheError 
} from '@actions/cache';

async function setupCacheWorkflow() {
  // Check if cache service is available
  if (!isFeatureAvailable()) {
    console.log('Cache service not available, skipping cache operations');
    return;
  }

  const paths = ['node_modules', 'packages/*/node_modules'];
  const primaryKey = `npm-deps-${process.platform}-${hashFiles('**/package-lock.json')}`;
  const restoreKeys = [`npm-deps-${process.platform}-`, 'npm-deps-'];

  let cacheHit = false;

  // Restore cache
  try {
    console.log('Attempting to restore cache...');
    const cacheKey = await restoreCache(paths, primaryKey, restoreKeys);
    
    if (cacheKey) {
      console.log(`✅ Cache restored from key: ${cacheKey}`);
      cacheHit = true;
    } else {
      console.log('❌ No cache found');
    }
  } catch (error) {
    if (error instanceof ValidationError) {
      console.error('❌ Cache restore validation failed:', error.message);
      process.exit(1); // Fail workflow on validation errors
    } else {
      console.warn('⚠️ Cache restore failed:', error.message);
      console.log('Continuing without cache...');
    }
  }

  // Install dependencies if cache miss
  if (!cacheHit) {
    console.log('Installing dependencies...');
    // await exec('npm ci');
  }

  // Save cache if it was a miss
  if (!cacheHit) {
    try {
      console.log('Saving cache...');
      const cacheId = await saveCache(paths, primaryKey);
      console.log(`✅ Cache saved with ID: ${cacheId}`);
    } catch (error) {
      if (error instanceof ValidationError) {
        console.error('❌ Cache save validation failed:', error.message);
        process.exit(1); // Fail workflow on validation errors
      } else if (error instanceof ReserveCacheError) {
        console.info('ℹ️ Cache reservation failed:', error.message);
        console.info('This usually means another job is creating the same cache');
      } else {
        console.warn('⚠️ Cache save failed:', error.message);
        console.log('Build will continue without caching');
      }
    }
  }
}

function hashFiles(pattern: string): string {
  // Implementation to hash files matching pattern
  // This would typically use a crypto hash of package-lock.json content
  return 'abc123def456'; // Placeholder
}

// Run the workflow
setupCacheWorkflow().catch(error => {
  console.error('Workflow failed:', error);
  process.exit(1);
});

Error Prevention Best Practices

Key Validation

Validate cache keys before using them:

function validateCacheKey(key: string): void {
  if (key.length > 512) {
    throw new ValidationError(`Cache key too long: ${key.length} characters (max 512)`);
  }
  if (key.includes(',')) {
    throw new ValidationError(`Cache key cannot contain commas: ${key}`);
  }
}

function createSafeCacheKey(base: string, hash: string): string {
  const key = `${base}-${hash}`;
  validateCacheKey(key);
  return key;
}

Path Validation

Ensure paths exist before caching:

import { existsSync } from 'fs';

function validatePaths(paths: string[]): void {
  if (paths.length === 0) {
    throw new ValidationError('At least one path is required');
  }
  
  const missingPaths = paths.filter(path => !existsSync(path));
  if (missingPaths.length > 0) {
    throw new ValidationError(`Paths do not exist: ${missingPaths.join(', ')}`);
  }
}

Graceful Degradation

Always design cache operations to be optional:

async function installDependencies() {
  let cacheHit = false;
  
  // Try to restore cache, but don't fail if it doesn't work
  try {
    const cacheKey = await restoreCache(['node_modules'], 'npm-deps');
    cacheHit = !!cacheKey;
  } catch (error) {
    console.warn('Cache restore failed, will install fresh dependencies');
  }
  
  // Install dependencies if no cache hit
  if (!cacheHit) {
    console.log('Installing dependencies...');
    // await exec('npm ci');
    
    // Try to save cache, but don't fail if it doesn't work
    try {
      await saveCache(['node_modules'], 'npm-deps');
    } catch (error) {
      console.warn('Cache save failed, build will continue');
    }
  }
}