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.md
source 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

PatternSeverity
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