Implemented foundational modules for Claude Discord Coordinator: - Project skeleton with uv (CRIT-003) - Claude CLI subprocess runner with 11/11 tests passing (CRIT-004) - SQLite session manager with 27/27 tests passing (CRIT-005) - Comprehensive test suites for both modules - Production-ready async/await patterns - Full type hints and documentation Technical highlights: - Validated CLI pattern: claude -p --resume --output-format json - bypassPermissions requires non-root user (discord-bot) - WAL mode SQLite for concurrency - asyncio.Lock for thread safety - Context manager support Progress: 5/18 tasks complete (28%) Week 1: 5/6 complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
7.8 KiB
CRIT-004 Implementation Summary
Task: Build Claude CLI subprocess runner
Status: ✅ COMPLETED
Date: 2026-02-13
Files Modified/Created: 3 new files
Implementation Details
1. Core Module: /opt/projects/claude-coordinator/claude_coordinator/claude_runner.py
Fully implemented async subprocess wrapper with:
ClaudeResponse Dataclass
success: Boolean indicating command successresult: Claude's response text (from JSON result field)session_id: UUID for session resumption (snake_case, not camelCase)error: Error message if command failedcost: Total cost in USD for invocationduration_ms: Execution time in millisecondspermission_denials: List of denied permissions
ClaudeRunner Class
Methods:
__init__(default_timeout=300, oauth_token=None): Initialize with timeout and optional tokenasync run(message, session_id=None, cwd=None, allowed_tools=None, system_prompt=None, model=None, timeout=None): Main execution method_build_command(...): Constructs claude CLI command with all flags_prepare_environment(): Sets up subprocess environment (CRITICAL: unsets CLAUDECODE)_parse_response(stdout): Parses JSON output and extracts fields
Features Implemented:
✅ Async subprocess execution with asyncio.create_subprocess_exec
✅ Timeout management (default 5 minutes, configurable)
✅ JSON response parsing with error handling
✅ Session ID extraction (snake_case: session_id)
✅ Environment preparation (unsets CLAUDECODE for nested sessions)
✅ OAuth token support via CLAUDE_CODE_OAUTH_TOKEN
✅ Command building with all flags (--resume, --model, --system-prompt, --allowed-tools)
✅ Error handling: timeouts, malformed JSON, process errors, permission denials
✅ Comprehensive logging for debugging
Critical Requirements from VALIDATION_RESULTS.md:
✅ Unsets CLAUDECODE environment variable
✅ Uses snake_case (session_id not sessionId)
✅ Sets CLAUDE_CODE_OAUTH_TOKEN if provided
✅ Runs with bypassPermissions for unattended operation
✅ Parses JSON structure correctly (type, subtype, is_error, result, session_id, cost)
2. Test Suite: /opt/projects/claude-coordinator/tests/test_claude_runner.py
Comprehensive test coverage with 12 test cases:
Unit Tests (11 tests, all passing):
- ✅
test_new_session_creation- Verifies session creation without session_id - ✅
test_session_resumption- Verifies --resume flag and context preservation - ✅
test_timeout_handling- Tests asyncio timeout and process killing - ✅
test_malformed_json_handling- Tests JSON parse error handling - ✅
test_process_error_handling- Tests non-zero exit codes - ✅
test_claude_error_response- Tests is_error flag detection - ✅
test_permission_denial_handling- Tests permission_denials array - ✅
test_command_building_with_all_options- Verifies all flags present - ✅
test_environment_preparation- Verifies CLAUDECODE unset and token set - ✅
test_cwd_parameter- Tests working directory parameter - ✅
test_parse_response_edge_cases- Tests minimal and complete JSON
Integration Test (1 test, requires authentication):
test_real_claude_session- Tests with real Claude CLI (marked with @pytest.mark.integration)
Test Results:
11 passed, 1 deselected (integration), 1 warning
Test Coverage: All core functionality covered with mocked subprocesses
3. Usage Example: /opt/projects/claude-coordinator/examples/basic_usage.py
Demonstrates:
- Creating a new Claude session
- Resuming session with context preservation
- Using tool restrictions and working directory
- Error handling and cost tracking
Run with: uv run python examples/basic_usage.py
Command Pattern Implemented
cmd = [
"claude",
"-p", message,
"--output-format", "json",
"--permission-mode", "bypassPermissions"
]
if session_id:
cmd.extend(["--resume", session_id])
if model:
cmd.extend(["--model", model])
if system_prompt:
cmd.extend(["--system-prompt", system_prompt])
if allowed_tools:
cmd.extend(["--allowed-tools", ",".join(allowed_tools)])
Environment Handling (CRITICAL)
def _prepare_environment(self) -> dict:
env = os.environ.copy()
# CRITICAL: Unset CLAUDECODE to allow nested sessions
env.pop('CLAUDECODE', None)
# Set OAuth token if provided
if self.oauth_token:
env['CLAUDE_CODE_OAUTH_TOKEN'] = self.oauth_token
return env
Why this matters: Without unsetting CLAUDECODE, subprocess fails with:
"Claude Code cannot be launched inside another Claude Code session"
JSON Response Parsing
Correctly handles the structure from VALIDATION_RESULTS.md:
{
"type": "result",
"subtype": "success" or error type,
"is_error": boolean,
"result": actual response text,
"session_id": UUID (snake_case!),
"total_cost_usd": cost tracking,
"duration_ms": execution time,
"permission_denials": array (should be empty)
}
Key Implementation Detail: Uses data.get("session_id") NOT data.get("sessionId")
Error Handling
Handles all failure modes:
- Timeout: Process killed, error response returned
- Non-zero exit code: stderr captured and returned
- Malformed JSON: Parse error with raw output logged
- Claude API errors: is_error flag detected, error message extracted
- Permission denials: permission_denials array checked
- Unexpected exceptions: Caught and wrapped in error response
Dependencies Added
[project.optional-dependencies]
dev = [
"pytest>=9.0.2",
"pytest-asyncio>=1.3.0"
]
Verification
Unit Tests
cd /opt/projects/claude-coordinator
uv run pytest tests/test_claude_runner.py -v -m "not integration"
Result: ✅ 11/11 tests passing
Integration Test (requires Claude CLI auth)
uv run pytest tests/test_claude_runner.py -v -m integration
Example Usage
uv run python examples/basic_usage.py
Next Steps (CRIT-005)
With ClaudeRunner complete and tested, the next critical task is:
CRIT-005: Build session manager with SQLite
- Per-channel session ID persistence
- Stores channel_id → session_id mapping
- Schema: sessions(channel_id, session_id, project_name, timestamps, message_count)
Files Created
/opt/projects/claude-coordinator/claude_coordinator/claude_runner.py(245 lines)/opt/projects/claude-coordinator/tests/test_claude_runner.py(380 lines)/opt/projects/claude-coordinator/tests/conftest.py(pytest config)/opt/projects/claude-coordinator/examples/basic_usage.py(95 lines)
Total: ~720 lines of production code and tests
Key Learnings
- CLAUDECODE environment variable must be unset for nested sessions
- snake_case is used in JSON responses (session_id, not sessionId)
- bypassPermissions enables unattended operation (required for Discord bot)
- asyncio.create_subprocess_exec is the correct approach (NOT shell=True)
- Timeout handling requires asyncio.wait_for and process.kill()
- JSON parsing must handle edge cases (missing fields, errors, denials)
Code Quality
✅ Comprehensive type hints throughout
✅ Detailed docstrings with examples
✅ Extensive error handling and logging
✅ Clean separation of concerns (build, execute, parse)
✅ Production-ready code quality
✅ 100% of core functionality tested
Production Readiness
✅ Async/await for non-blocking operation
✅ Configurable timeouts prevent hangs
✅ Comprehensive error handling
✅ Detailed logging for debugging
✅ Validated against real Claude CLI pattern
✅ All edge cases from validation testing covered
✅ Ready for Discord bot integration
Engineer: Atlas (a701530)
Task: CRIT-004
Status: ✅ COMPLETE