Skill: lhl09120/claude-code-runner (version 0.1.0)

--- name: claude-code-runner description: "Execute programming tasks via Claude Code using PTY-based invocation. Handles non-TTY environments, auto-responds to prompts, and manages file synchronization." --- # Claude Code Runner ## Overview A wrapper skill for running Claude Code programmatically in non-interactive environments. Uses PTY (pseudo-terminal) to handle TTY-required operations and automatically responds to confirmation prompts. ## Features - **PTY-based execution**: Works in non-TTY environments (containers, CI/CD, background processes) - **Auto-respond to prompts**: Automatically answers "Do you want to..." confirmations - **User switching**: Runs as specified non-root user - **File synchronization**: Copies project to temp directory, executes, syncs changes back - **Timeout handling**: Configurable timeout with proper cleanup - **Output capture**: Captures and returns full stdout/stderr ## Installation ```bash # Clone the skill git clone https://github.com/lhl09120/claude-code-runner-en.git # Make script executable chmod +x claude-code-runner-en/scripts/run_claude.py ``` ## Usage ### Basic Usage ```python from claude_code_runner import run_claude_code result = run_claude_code( workdir='/path/to/project', prompt='Refactor the authentication module to use JWT tokens', user='lighthouse', timeout=300 ) print(result) ``` ### Via Command Line ```bash python3 scripts/run_claude.py /path/to/project "Your task description here" ``` ### Advanced Options ```python result = run_claude_code( workdir='/root/repo/my-project', prompt=''' 1. Review the codebase 2. Identify security vulnerabilities 3. Fix any issues found 4. Add appropriate tests ''', user='developer', timeout=600 # 10 minutes ) ``` ## API Reference ### `run_claude_code(workdir, prompt, user='lighthouse', timeout=300)` Execute a Claude Code task in a PTY environment. **Parameters:** - `workdir` (str): Working directory containing the project - `prompt` (str): Natural language task description - `user` (str): User to run as (default: 'lighthouse') - `timeout` (int): Timeout in seconds (default: 300) **Returns:** - `str`: Combined stdout and stderr output **Behavior:** 1. Copies project to temporary directory 2. Changes ownership to specified user 3. Executes Claude Code via PTY 4. Auto-responds to confirmation prompts 5. Syncs changes back to original directory 6. Cleans up temporary files ## Use Cases ### 1. Automated Code Review ```python result = run_claude_code( workdir='/root/repo/project', prompt='Review this codebase and identify potential bugs or improvements' ) ``` ### 2. Refactoring Tasks ```python result = run_claude_code( workdir='/root/repo/legacy-app', prompt='Refactor the database layer to use SQLAlchemy ORM instead of raw SQL' ) ``` ### 3. Adding Features ```python result = run_claude_code( workdir='/root/repo/api-service', prompt=''' Add a new REST endpoint for user profile management: - GET /api/users/{id}/profile - PUT /api/users/{id}/profile - Include validation and error handling - Add unit tests ''' ) ``` ### 4. Bug Fixes ```python result = run_claude_code( workdir='/root/repo/web-app', prompt='Fix the memory leak in the WebSocket connection handler' ) ``` ## Requirements - Python 3.8+ - Claude Code installed and in PATH - Unix-like environment (Linux/macOS) - Root or sudo access (for user switching) ## Configuration ### Environment Variables - `CLAUDE_CODE_USER`: Default user to run as (default: 'lighthouse') - `CLAUDE_CODE_TIMEOUT`: Default timeout in seconds (default: 300) ### Customization Edit `scripts/run_claude.py` to customize: - Auto-response keywords - Temp directory location - Sync behavior - Output formatting ## Troubleshooting ### "Permission denied" errors Ensure the script is run with sufficient privileges to: - Create temporary directories - Change file ownership - Switch to target user ### Claude Code not found Make sure Claude Code is installed and in the system PATH: ```bash which claude ``` ### Task timeout Increase the timeout for long-running tasks: ```python run_claude_code(workdir, prompt, timeout=600) # 10 minutes ``` ### Interactive prompts not auto-responded Add new prompt patterns to the auto-respond logic: ```python if b'new prompt text' in output: os.write(master_fd, b'y\n') ``` ## Limitations - Requires Unix-like environment (uses PTY) - Requires root/sudo for user switching - Claude Code must be installed separately - May not handle all edge cases of interactive prompts ## License MIT License Copyright (c) 2026 lhl09120 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. See LICENSE file for full details. ## Changelog ### v1.0.0 (2026-02-27) - Initial release - PTY-based Claude Code execution - Auto-response to confirmation prompts - File synchronization - User switching support

path: README.md
content: # Claude Code Runner

A wrapper for running Claude Code in non-interactive environments using PTY (pseudo-terminal).

## Features

- ✅ **PTY Execution**: Run in non-TTY environments (containers, CI/CD, background processes)
- ✅ **Auto-Response**: Automatically answer confirmation prompts ("Do you want to...")
- ✅ **User Switching**: Run as specified non-root user
- ✅ **File Synchronization**: Automatically sync changes back to original directory
- ✅ **Timeout Handling**: Configurable timeout with proper cleanup
- ✅ **Output Capture**: Capture and return full stdout/stderr

## Installation

```bash
git clone https://github.com/lhl09120/claude-code-runner-en.git
cd claude-code-runner-en
chmod +x scripts/run_claude.py
```

## Quick Start

### Python API

```python
from scripts.run_claude import run_claude_code

result = run_claude_code(
    workdir='/path/to/project',
    prompt='Refactor the authentication module',
    user='lighthouse',
    timeout=300
)
```

### Command Line

```bash
python3 scripts/run_claude.py /path/to/project "Your task description"
```

## Usage Examples

### Code Review

```python
run_claude_code(
    workdir='/root/repo/project',
    prompt='Review this codebase and identify potential bugs'
)
```

### Add Features

```python
run_claude_code(
    workdir='/root/repo/api',
    prompt='''
    Add a new REST endpoint:
    - GET /api/users/{id}/profile
    - Include validation and tests
    '''
)
```

### Bug Fix

```python
run_claude_code(
    workdir='/root/repo/app',
    prompt='Fix the memory leak in WebSocket handler'
)
```

## Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `workdir` | str | - | Project working directory |
| `prompt` | str | - | Natural language task description |
| `user` | str | 'lighthouse' | User to run as |
| `timeout` | int | 300 | Timeout in seconds |

## Dependencies

- Python 3.8+
- Claude Code (installed and in PATH)
- Unix-like environment (Linux/macOS)
- root or sudo privileges (for user switching)

## How It Works

1. **Copy Project** → to temporary directory
2. **Change Permissions** → switch to target user
3. **PTY Execute** → run Claude Code
4. **Auto-Response** → handle confirmation prompts
5. **Sync Changes** → write changes back to original directory
6. **Cleanup** → delete temporary files

## License

MIT License

Copyright (c) 2026 lhl09120

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

See LICENSE file for full details.


path: clawhub.json
content: {
  "name": "claude-code-runner-en",
  "version": "1.0.0",
  "description": "Execute programming tasks via Claude Code using PTY-based invocation (English version)",
  "author": "lhl09120",
  "entry": {
    "python": "scripts/run_claude.py"
  },
  "scripts": [
    "scripts/run_claude.py"
  ],
  "dependencies": {
    "python": ">=3.8",
    "tools": ["claude"]
  },
  "tags": ["claude-code", "automation", "programming", "pty", "english"],
  "license": "MIT"
}


path: scripts/run_claude.py
content: #!/usr/bin/env python3
"""
Claude Code Runner - PTY-based Claude Code execution wrapper

Run Claude Code in non-interactive environments using pseudo-terminal (PTY),
automatically handle confirmation prompts, and sync file changes.
"""

import os
import pty
import select
import subprocess
import sys
import tempfile
import shutil
import time


def run_claude_code(workdir, prompt, user='lighthouse', timeout=300):
    """
    Run Claude Code task using PTY
    
    Args:
        workdir: Working directory path
        prompt: Natural language task description
        user: User to run as (default: lighthouse)
        timeout: Timeout in seconds (default: 300)
        
    Returns:
        Command output (stdout + stderr)
    """
    # Create temporary directory
    temp_dir = tempfile.mkdtemp(prefix='claude-task-')
    project_name = os.path.basename(os.path.abspath(workdir))
    temp_workdir = os.path.join(temp_dir, project_name)
    
    try:
        # Copy project to temporary directory
        print(f"[INFO] Copying project to temp directory: {temp_workdir}", file=sys.stderr)
        shutil.copytree(workdir, temp_workdir, ignore=shutil.ignore_patterns('.git', '__pycache__', '*.pyc'))
        
        # Change permissions
        shutil.chown(temp_dir, user=user, group=user)
        for root, dirs, files in os.walk(temp_workdir):
            for d in dirs:
                shutil.chown(os.path.join(root, d), user=user, group=user)
            for f in files:
                shutil.chown(os.path.join(root, f), user=user, group=user)
        
        # Create PTY
        master_fd, slave_fd = pty.openpty()
        
        # Set environment variables
        env = os.environ.copy()
        env['TERM'] = 'xterm-256color'
        env['HOME'] = f'/home/{user}'
        
        # Build command
        cmd = [
            'su', '-', user, '-c',
            f'cd {temp_workdir} && claude --print "{prompt}" 2>&1'
        ]
        
        print(f"[INFO] Starting Claude Code task...", file=sys.stderr)
        
        # Start process
        process = subprocess.Popen(
            cmd,
            stdin=slave_fd,
            stdout=slave_fd,
            stderr=slave_fd,
            env=env,
            preexec_fn=os.setsid
        )
        
        os.close(slave_fd)
        output = b''
        
        try:
            while True:
                readable, _, _ = select.select([master_fd], [], [], timeout)
                if master_fd in readable:
                    try:
                        data = os.read(master_fd, 4096)
                        if not data:
                            break
                        output += data
                        # Real-time output to stderr
                        print(data.decode('utf-8', errors='replace'), end='', flush=True, file=sys.stderr)
                        
                        # Auto-respond to confirmation prompts
                        if b'Do you want to' in output or b'proceed' in output.lower() or b'continue' in output.lower():
                            print("[INFO] Detected confirmation prompt, auto-responding 'y'", file=sys.stderr)
                            time.sleep(0.5)
                            os.write(master_fd, b'y\n')
                            output = b''
                            
                    except OSError:
                        break
                
                if process.poll() is not None:
                    break
                    
        finally:
            os.close(master_fd)
            if process.poll() is None:
                process.terminate()
                process.wait()
        
        exit_code = process.returncode
        
        # Sync changes back to original directory
        if exit_code == 0:
            print(f"[INFO] Syncing changes back to original directory...", file=sys.stderr)
            _sync_changes(temp_workdir, workdir)
        
        return output.decode('utf-8', errors='replace')
        
    finally:
        # Cleanup temporary directory
        print(f"[INFO] Cleaning up temporary directory...", file=sys.stderr)
        shutil.rmtree(temp_dir, ignore_errors=True)


def _sync_changes(source_dir, target_dir):
    """
    Sync changes from temporary directory back to original directory
    
    Args:
        source_dir: Source directory (temp directory)
        target_dir: Target directory (original project directory)
    """
    for root, dirs, files in os.walk(source_dir):
        # Calculate relative path
        rel_path = os.path.relpath(root, source_dir)
        target_root = os.path.join(target_dir, rel_path) if rel_path != '.' else target_dir
        
        # Ensure target directory exists
        if not os.path.exists(target_root):
            os.makedirs(target_root)
        
        for file in files:
            source_file = os.path.join(root, file)
            target_file = os.path.join(target_root, file)
            
            # Copy if file doesn't exist or has been modified
            if not os.path.exists(target_file):
                print(f"[SYNC] Added: {os.path.join(rel_path, file) if rel_path != '.' else file}", file=sys.stderr)
                shutil.copy2(source_file, target_file)
            elif os.path.getmtime(source_file) > os.path.getmtime(target_file):
                print(f"[SYNC] Updated: {os.path.join(rel_path, file) if rel_path != '.' else file}", file=sys.stderr)
                shutil.copy2(source_file, target_file)


def main():
    """Command line entry point"""
    if len(sys.argv) < 3:
        print("Usage: python3 run_claude.py <workdir> <prompt> [user] [timeout]", file=sys.stderr)
        print("Example: python3 run_claude.py /root/repo/my-project 'Add user authentication' lighthouse 300", file=sys.stderr)
        sys.exit(1)
    
    workdir = sys.argv[1]
    prompt = sys.argv[2]
    user = sys.argv[3] if len(sys.argv) > 3 else 'lighthouse'
    timeout = int(sys.argv[4]) if len(sys.argv) > 4 else 300
    
    if not os.path.exists(workdir):
        print(f"Error: Directory not found: {workdir}", file=sys.stderr)
        sys.exit(1)
    
    result = run_claude_code(workdir, prompt, user, timeout)
    print(result)


if __name__ == "__main__":
    main()


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: suspicious | SkillSpector: suspicious (HIGH)