# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview This is a modular Node.js webhook server for Gogs (Git service) that receives webhooks and executes configurable shell commands based on events. The main server uses **zero external dependencies** - only built-in Node.js modules. **NEW:** The project now includes a TypeScript-based Claude Agent SDK client (`client/`) for AI-powered issue automation, replacing the previous bash-based implementation. ## Architecture ### Module Structure The codebase follows a clean separation of concerns with five main modules: ``` index.js (Entry Point) ├── server.js (HTTP Server) │ └── webhookHandler.js (Event Management) ├── jobQueue.js (Job Queue & Persistence) │ └── commandExecutor.js (Command Execution) ├── config.js (Configuration) └── logger.js (Logging) ``` ### Key Components **WebhookServer (src/server.js)** - HTTP server using Node.js `http` module - Routes: `POST /webhook`, `GET /health` - Provides `getHandler()` to access WebhookHandler for callback registration - Binds to configurable host (0.0.0.0 by default) and port (3000 by default) **WebhookHandler (src/webhookHandler.js)** - Event-driven callback system - Two callback types: - `handler.on(eventType, callback)` - Event-specific callbacks - `handler.onAny(callback)` - Global callbacks for all events - Callbacks receive: `(payload, headers)` or `(eventType, payload, headers)` for global **JobQueue (src/jobQueue.js)** - Persistent job queue with FIFO (first-in-first-out) processing - Ensures only one job executes at a time to prevent conflicts - Webhooks are accepted immediately and added to queue - Queue state persisted to `queue.json` for crash recovery - Automatically resumes processing pending jobs after restart - Jobs that were running during shutdown are reset to pending state **CommandExecutor (src/commandExecutor.js)** - Variable substitution engine using `{{variable}}` syntax - Executes shell commands via `/bin/bash` with timeout protection - Extracts 16+ variables from webhook payloads: `{{repo}}`, `{{branch}}`, `{{pusher}}`, `{{commit}}`, `{{pr_number}}`, `{{issue_number}}`, `{{issue_title}}`, etc. - Default timeout: 300000ms (5 minutes), configurable via `COMMAND_TIMEOUT` **Config (src/config.js)** - Loads `.env` file synchronously on startup - Merges with `process.env` (env vars take precedence) - Provides type-safe getters: `get()`, `getBoolean()`, `getNumber()` - Exported as singleton **Logger (src/logger.js)** - Console-based logger with ANSI colors and timestamps - Methods: `info()`, `error()`, `webhook()` - Exported as singleton ### Application Flow 1. `index.js` loads config and creates WebhookServer 2. JobQueue loads persisted queue state from `queue.json` (if exists) 3. Gets enabled events from config (`WEBHOOK_PUSH_ENABLED=true`, etc.) 4. Registers callbacks for enabled events (webhooks → enqueue jobs) 5. Starts server on configured host:port 6. On webhook: parses body → triggers callbacks → enqueues job → responds immediately 7. JobQueue processes jobs sequentially (one at a time, FIFO) 8. Each job: extracts variables → substitutes in command → executes → logs results 9. Graceful shutdown on SIGTERM/SIGINT: saves queue state and stops server ## Development Commands ```bash npm start # Start production server npm run dev # Start with auto-reload (--watch flag) node examples/with-callbacks.js # Run callback example ``` **Testing endpoints:** ```bash curl http://localhost:3000/health curl -X POST http://localhost:3000/webhook \ -H "Content-Type: application/json" \ -H "X-Gogs-Event: push" \ -H "X-Gogs-Delivery: 12345" \ -d '{"ref":"refs/heads/main","repository":{"name":"test-repo","full_name":"user/test-repo"},"pusher":{"username":"testuser"},"commits":[{"id":"abc123","message":"Test commit"}]}' ``` ## Configuration Configuration is split between two files: ### 1. Environment Configuration (`.env`) Copy from `.env.example` and configure: **Server Configuration:** ```env HOST=0.0.0.0 # Network interface (0.0.0.0=all, 127.0.0.1=localhost only) PORT=3000 # Port number WEBHOOK_PATH=/webhook # Webhook endpoint path WEBHOOK_SECRET= # Optional webhook verification secret ``` **Event Enable/Disable:** ```env WEBHOOK_{EVENT_TYPE}_ENABLED=true|false ``` **Supported Event Types:** push, pull_request, create, delete, release, issues, issue_comment, global **MCP Server Configuration:** ```env GOGS_MCP_PATH=/data/gogs-mcp/dist/index.js # Path to gogs-mcp server for headless Claude Code ``` The `GOGS_MCP_PATH` environment variable configures the location of the gogs-mcp server used by Claude Code in headless mode for issue automation. The handle-issue.sh script dynamically generates the MCP configuration file (.mcp-gogs.json) using this path at runtime, making it environment-specific. ### 2. Command Configuration (`commands.json`) Copy from `commands.json.example` and configure commands for each event type. **Command Structure:** ```json { "commands": { "event_type": [ { "name": "command-identifier", "description": "Human-readable description", "type": "shell" | "node", "command": "path/to/command", "args": ["arg1", "{{variable}}", "arg3"], "cwd": "/working/directory" | null, "timeout": 300000, "filterBranch": "branch-name" | null } ] } } ``` **Command Types:** - `shell` - Execute bash shell command/script - `node` - Execute Node.js script (automatically prepends `node` command) **Per-Command Settings:** - `name` - Unique identifier for the command - `description` - Human-readable description - `type` - Execution type (shell or node) - `command` - Command or script path - `args` - Array of arguments (supports variable substitution) - `cwd` - Working directory (null = project root) - `timeout` - Timeout in milliseconds - `filterBranch` - Only execute for specific branch (null = all branches) ## Important Patterns 1. **ES6 Modules**: All files use `import/export` syntax (`"type": "module"` in package.json) 2. **Singleton Exports**: Config, Logger, CommandExecutor are singletons (created on import) 3. **Zero Dependencies**: Pure Node.js - no npm packages required beyond Node.js built-ins 4. **Graceful Shutdown**: Both SIGTERM and SIGINT trigger `server.stop()` before exit 5. **Configuration Files**: `.env` for enable/disable flags, `commands.json` for command definitions 6. **Backward Compatibility**: Legacy .env command format still supported (deprecated) 7. **Error Handling**: Try-catch blocks with logging; errors don't stop server 8. **Branch Filtering**: Optional per-command branch filtering via `filterBranch` field 9. **Multiple Commands**: Each event type can have multiple commands that execute sequentially 10. **Shell & Node Execution**: Commands can be shell scripts or Node.js scripts ## Extending with Callbacks The server supports custom callback registration beyond .env command execution: ```javascript import { WebhookServer } from './src/server.js'; const server = new WebhookServer({ port: 3000 }); const handler = server.getHandler(); // Event-specific callback handler.on('push', async (payload, headers) => { console.log('Push to:', payload.repository.name); // Custom logic here }); // Global callback (all events) handler.onAny(async (eventType, payload, headers) => { console.log('Event:', eventType); // Send to analytics, logging, etc. }); server.start(); ``` See `examples/with-callbacks.js` for complete example. ## Variable Substitution System Commands in `commands.json` and legacy `.env` commands support variable substitution using `{{variable}}` syntax. **Available variables:** - `{{event}}` - Event type (push, create, delete, etc.) - `{{repo}}` - Repository name only - `{{full_repo}}` - User/repository format - `{{repo_owner}}` - Repository owner username - `{{branch}}` - Branch name (extracted from refs) - `{{pusher}}` - Username of person who triggered event - `{{commit}}` - Short commit hash (push events) - `{{commit_msg}}` - Last commit message (push events) - `{{pr_number}}` - Pull request number (PR events) - `{{pr_action}}` - PR action (opened, closed, etc.) - `{{tag}}` - Tag name (create/delete tag events) - `{{ref_type}}` - "branch" or "tag" - `{{issue_number}}` - Issue number (issue events) - `{{issue_title}}` - Issue title (issue events) - `{{issue_action}}` - Issue action (opened, closed, reopened, etc.) - `{{issue_body}}` - Issue description/body (issue events) - `{{issue_assignee}}` - Username of person assigned to issue (issue events) - `{{comment_body}}` - Comment text (issue_comment events) Variables are extracted in `commandExecutor.js` via `extractVariables()` and substituted via `substituteVariables()`. ## Job Queue System The webhook server uses a persistent job queue to ensure reliable command execution: ### Features - **Sequential Execution**: Only one job runs at a time, preventing conflicts and resource contention - **FIFO Processing**: Jobs are executed in the order they are received - **Immediate Response**: Webhooks are accepted and queued instantly, ensuring no webhook delivery timeouts - **Persistence**: Queue state is saved to `queue.json` after every change - **Crash Recovery**: Pending jobs are automatically restored and processed after server restart - **Safe Restart**: Jobs marked as "running" during shutdown are reset to "pending" on restart ### Queue File Structure ```json { "queue": [ { "id": 1, "eventType": "push", "status": "pending", "createdAt": "2025-10-28T12:00:00.000Z", "config": { "command": "...", "enabled": true } } ], "jobIdCounter": 5, "lastSaved": "2025-10-28T12:05:00.000Z" } ``` ### Job Lifecycle 1. **Created**: Webhook received → Job created with status "pending" 2. **Queued**: Job added to queue and persisted to disk 3. **Running**: Job status updated to "running" before execution starts 4. **Completed/Failed**: Job finishes → Status updated → Job removed from queue 5. **Next**: Queue processes next pending job (if any) ### Benefits - **No Lost Jobs**: Server crashes won't lose pending webhook events - **No Concurrent Execution**: Prevents race conditions in deployment scripts - **High Availability**: Can accept webhooks even while executing long-running commands - **Scalable**: Queue can grow to handle bursts of webhook activity ## TypeScript Client for Issue Automation The `client/` directory contains a TypeScript-based Claude Agent SDK client that provides AI-powered issue automation: **Location**: `client/` **Key Files**: - `src/claude-client.ts` - Main Claude Agent SDK client - `src/config.ts` - Configuration management - `src/types.ts` - TypeScript type definitions - `src/index.ts` - CLI entry point - `dist/` - Compiled JavaScript (auto-generated by `npm run build`) **Usage**: ```bash cd client npm install npm run build ``` **Integration**: The TypeScript client is called from `scripts/handle-issue-ts.sh`, which is configured in `commands.json` for `issues` and `issue_comment` events. **Features**: - Integrates with gogs-mcp MCP server for Gogs API access - Processes issues assigned to Claude - Responds to issue comments automatically - Environment-specific configuration via `.env` - **Per-repository MCP configuration support** - Different repositories can use different MCP servers See `client/README.md` for detailed documentation. ### MCP Configuration System The client supports flexible MCP server configuration with three priority levels: 1. **Repository-specific config**: `~/.config/agent-manager/mcp/{owner}/{repo}.json` 2. **Global default config**: `~/.config/agent-manager/mcp/default.json` 3. **Hardcoded fallback**: Built-in gogs-mcp configuration (automatic) **Key Features**: - Multiple MCP servers per repository - Environment variable substitution using `${VAR_NAME}` syntax - Secure storage with restricted file permissions (600) - Backward compatible with existing hardcoded configuration **Configuration Structure**: ```json { "mcpServers": { "gogs": { "type": "stdio", "command": "node", "args": ["/data/gogs-mcp/dist/index.js"], "env": {} }, "custom-mcp": { "type": "stdio", "command": "node", "args": ["/path/to/custom-mcp/dist/index.js"], "env": { "API_KEY": "${MY_API_KEY}" } } } } ``` **Security**: Configuration files are stored outside repositories in `~/.config/agent-manager/mcp/` with restricted permissions to protect secrets like API keys and JWT tokens. See `examples/MCP-CONFIG.md` for detailed configuration guide and examples. ## Code Modification Guidelines **Main Server (src/)**: - Maintain zero-dependency philosophy (use only Node.js built-ins) - Follow singleton pattern for utilities (Config, Logger, CommandExecutor) - Use async/await for all async operations - Add error handling with try-catch and logger.error() - Update `.env.example` when adding new configuration options - Update README.md when adding new features - Follow existing naming conventions: camelCase for variables/functions, PascalCase for classes - Export singletons with `export default new ClassName()` - Keep modules focused on single responsibility ## Testing No formal test suite currently. Test manually using: 1. Start server with `npm run dev` 2. Use curl to send webhook payloads 3. Check console logs for execution results 4. Verify commands execute with expected variable substitution You always have to push the changes into the git reposiroty. Before pushing changes, you have to test the project with building and running it. Take care when running the project to use an unused port if the project already running. Never keep running instance from the project ## Issue handling - always update the issue which you works on - always add label to the issue if no label added - always write comment with the current status of the work - when you create commit message, mention the issue if exists - never close the issue