Awesome-claude-code check-scalability-readiness
Analyzes PHP code for scalability issues. Detects file-based sessions, in-memory state, hardcoded hostnames, filesystem-dependent state, and missing stateless design patterns.
install
source · Clone the upstream repo
git clone https://github.com/dykyi-roman/awesome-claude-code
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/dykyi-roman/awesome-claude-code "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/check-scalability-readiness" ~/.claude/skills/dykyi-roman-awesome-claude-code-check-scalability-readiness && rm -rf "$T"
manifest:
skills/check-scalability-readiness/SKILL.mdsource content
Scalability Readiness Check
Analyze PHP code for patterns that prevent horizontal scaling, break in multi-instance deployments, or create single points of failure.
Detection Patterns
1. File-Based Sessions
<?php declare(strict_types=1); // BAD: Default file-based session storage // php.ini: session.save_handler = files session_start(); $_SESSION['user_id'] = $userId; // Instance A stores session, Instance B cannot read it // BAD: Direct $_SESSION usage without external store final class AuthService { public function login(User $user): void { $_SESSION['user'] = serialize($user); $_SESSION['logged_in'] = true; // Sticky sessions required, scaling limited } } // GOOD: Redis-backed session storage // php.ini or runtime: // session.save_handler = redis // session.save_path = "tcp://redis:6379" // GOOD: Token-based stateless authentication final readonly class JwtAuthenticator { public function __construct( private JwtEncoder $encoder, ) {} public function authenticate(Request $request): UserToken { $jwt = $request->headers->get('Authorization'); $payload = $this->encoder->decode($jwt); return new UserToken( userId: new UserId($payload['sub']), roles: $payload['roles'], ); // No server-side session state required } }
2. In-Memory State and Singleton Cache
<?php declare(strict_types=1); // BAD: Static in-memory cache final class ConfigService { private static array $cache = []; public static function get(string $key): mixed { if (!isset(self::$cache[$key])) { self::$cache[$key] = self::loadFromDatabase($key); } return self::$cache[$key]; // Each instance has different cache state // Cache invalidation does not propagate } } // BAD: Global mutable state $GLOBALS['app_state'] = ['initialized' => true]; // BAD: Singleton with mutable state final class Registry { private static ?self $instance = null; private array $data = []; public static function getInstance(): self { return self::$instance ??= new self(); } public function set(string $key, mixed $value): void { $this->data[$key] = $value; // Lost between requests in FPM, inconsistent across instances } } // GOOD: External cache (Redis) final readonly class ConfigService { public function __construct( private CacheInterface $cache, private ConfigRepository $repository, ) {} public function get(string $key): mixed { return $this->cache->get( 'config:' . $key, fn () => $this->repository->findByKey($key), ); // All instances share the same cache } }
3. Hardcoded Hostnames and Addresses
<?php declare(strict_types=1); // BAD: Hardcoded hostnames final class DatabaseConfig { private string $host = 'localhost'; private int $port = 3306; private string $redisHost = '127.0.0.1'; } // BAD: Hardcoded service URLs final readonly class ApiClient { public function call(): Response { return $this->http->get('http://192.168.1.100:8080/api/data'); } } // GOOD: Environment-driven configuration final readonly class DatabaseConfig { public function __construct( private string $host, private int $port, private string $redisHost, ) {} public static function fromEnvironment(): self { return new self( host: getenv('DB_HOST') ?: throw new \RuntimeException('DB_HOST not set'), port: (int) (getenv('DB_PORT') ?: '3306'), redisHost: getenv('REDIS_HOST') ?: throw new \RuntimeException('REDIS_HOST not set'), ); } }
4. Filesystem State and Local File Storage
<?php declare(strict_types=1); // BAD: Local filesystem for state storage final class QueueService { public function enqueue(Job $job): void { file_put_contents( '/tmp/queue/' . uniqid() . '.job', serialize($job), ); // Other instances cannot read this file } } // BAD: File-based locking final class LockService { public function acquire(string $key): bool { $fp = fopen('/tmp/locks/' . $key . '.lock', 'c'); return flock($fp, LOCK_EX | LOCK_NB); // Lock is local to this filesystem/instance } } // BAD: Local file uploads move_uploaded_file($tmpName, '/var/www/uploads/' . $filename); // Only accessible on this instance // GOOD: External storage for uploads final readonly class FileUploadService { public function __construct( private FilesystemOperator $storage, // S3, GCS, or MinIO ) {} public function upload(UploadedFile $file): string { $path = sprintf('uploads/%s/%s', date('Y/m/d'), $file->hashName()); $this->storage->write($path, $file->getContent()); return $path; } } // GOOD: Distributed locking final readonly class DistributedLockService { public function __construct( private LockFactory $lockFactory, // Redis-backed ) {} public function acquire(string $key, int $ttl = 30): Lock { $lock = $this->lockFactory->createLock($key, $ttl); $lock->acquire(true); return $lock; } }
5. Missing Shared-Nothing Architecture
<?php declare(strict_types=1); // BAD: Process depends on local cron schedule // crontab: * * * * * php /var/www/artisan schedule:run // Multiple instances = duplicate cron execution // GOOD: Distributed cron with lock final readonly class ScheduledTaskRunner { public function __construct( private LockFactory $lockFactory, ) {} public function runOnce(string $taskName, callable $task): void { $lock = $this->lockFactory->createLock('cron:' . $taskName, 300); if (!$lock->acquire(false)) { return; // Another instance is running this task } try { $task(); } finally { $lock->release(); } } }
Grep Patterns
# File-based sessions Grep: "\\\$_SESSION|session_start\(\)|session\.save_handler\s*=\s*files" --glob "**/*.php" Grep: "session\.save_handler" --glob "**/*.ini" # Static in-memory cache Grep: "private static.*\\\$cache|private static.*=\s*\[\]|static.*\\\$instance" --glob "**/*.php" # Global state Grep: "\\\$GLOBALS|global\s+\\\$" --glob "**/*.php" # Hardcoded hostnames Grep: "localhost|127\.0\.0\.1|192\.168\.|10\.0\." --glob "**/src/**/*.php" Grep: "'localhost'|\"localhost\"|'127\.0\.0\.1'" --glob "**/*.php" # Local file operations for state Grep: "file_put_contents\(|file_get_contents\(.*tmp|flock\(" --glob "**/src/**/*.php" Grep: "move_uploaded_file\(" --glob "**/*.php" # Local filesystem paths Grep: "/tmp/|/var/www/uploads|/var/log/" --glob "**/src/**/*.php" # Check for shared-nothing patterns Grep: "FilesystemOperator|S3Client|Flysystem" --glob "**/*.php" Grep: "RedisAdapter|Redis.*session|PredisClient" --glob "**/*.php"
Severity Classification
| Pattern | Severity |
|---|---|
| File-based sessions in production | 🔴 Critical |
| Local filesystem for state storage | 🔴 Critical |
| Hardcoded hostnames in source code | 🟠 Major |
| Static in-memory cache for shared data | 🟠 Major |
| Local file uploads without external storage | 🟠 Major |
| Global mutable state | 🟠 Major |
| Missing distributed cron lock | 🟡 Minor |
| Singleton pattern with mutable state | 🟡 Minor |
Output Format
### Scalability Issue: [Brief Description] **Severity:** 🔴/🟠/🟡 **Location:** `file.php:line` **Type:** [File Session|In-Memory State|Hardcoded Host|Filesystem State|Shared-Nothing] **Issue:** [Description of the scalability blocker] **Impact:** - Cannot scale beyond single instance - State lost on instance restart/replacement - Inconsistent behavior in multi-instance deployment **Code:** ```php // Non-scalable pattern
Fix:
// Horizontally scalable pattern
## When This Is Acceptable - **Single-instance deployment** -- Small applications that will never require horizontal scaling - **CLI tools** -- Command-line scripts that run on a single machine - **Development environment** -- Local development uses file sessions and localhost - **Static configuration** -- Immutable static arrays used as lookup tables (not mutable state) ### False Positive Indicators - Static array is a constant lookup table (no mutations) - File operations are for logging to stdout/stderr (Docker best practice) - localhost appears only in test fixtures or development configuration - $_SESSION usage is in a legacy adapter with Redis configured at runtime