# 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` and `crypto` modules - 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) - Implements HMAC-SHA256 signature verification for webhook security - Uses timing-safe comparison to prevent timing attacks **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 ``` ## System Management (Production) The agent-manager service runs as a systemd service on the production system. **Restart the service:** ```bash sudo /bin/systemctl restart agent-manager ``` **Check service status:** ```bash sudo /bin/systemctl status agent-manager ``` **View service logs:** ```bash sudo journalctl -u agent-manager -f ``` **Note**: The `claude` user has passwordless sudo access to restart the agent-manager service via `/etc/sudoers.d/50-agent-manager` configuration. **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, "filterActions": ["action1", "action2"] | null, "filterAssignee": "username" | 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) - `filterActions` - Only execute for specific actions (array, optional). Examples: `["assigned", "opened"]`, `["synchronize"]`. If omitted or empty, execute for all actions - `filterAssignee` - Only execute when issue/PR is assigned to specific user (string, optional). If omitted, execute regardless of assignee ## 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. **Command Filtering**: Optional per-command filtering via: - `filterBranch` - Execute only for specific branches - `filterActions` - Execute only for specific actions (e.g., `["assigned", "opened"]`) - `filterAssignee` - Execute only when assigned to specific user (e.g., `"claude"`) 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/repository-manager.ts` - Git repository operations (clone, pull, environment setup) - `src/gogs-helper.ts` - Gogs API helper functions - `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 directly from `commands.json` for `issues` and `issue_comment` events. No shell script wrapper is needed. **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 - **Dynamic tool discovery** - Automatically discovers available tools from all configured MCP servers - **Automatic repository management** - Clones repositories on demand and pulls latest changes - **Environment setup** - Handles HOME, PATH, and XDG directory configuration - **Git URL from Gogs API** - Fetches SSH URL dynamically from Gogs instead of hardcoding 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) - **Automatic tool discovery** - Tools from custom MCP servers are automatically available to Claude - No need to manually configure allowed tools **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. ### Dynamic Tool Discovery The client automatically discovers available tools from all configured MCP servers at runtime: **How It Works**: 1. When the client starts, it loads the MCP configuration for the repository 2. The `ToolDiscovery` class connects to each configured MCP server 3. It queries each server using the MCP `tools/list` method 4. Tool names are formatted as `mcp__{server-name}__{tool-name}` 5. Built-in Claude Code tools (Read, Write, Edit, Bash, etc.) are added automatically 6. The complete list is passed to the Claude Agent SDK as `allowedTools` **Benefits**: - **No manual configuration** - Add a custom MCP server and its tools are immediately available - **Repository-specific tools** - Different repos can have different tool sets based on their MCP config - **Always up-to-date** - Tool list reflects the actual capabilities of configured servers **Built-in Tools** (always available): - `Read`, `Write`, `Edit` - File operations - `Bash`, `Grep`, `Glob` - Shell operations - `TodoWrite`, `Task` - Task management - `WebFetch`, `WebSearch` - Web access - `NotebookEdit`, `BashOutput`, `KillShell` - Advanced operations - `Skill`, `SlashCommand` - Extension points **Example**: If you configure a custom MCP server named "github" with tools "create_pr" and "list_repos", they will automatically be available as `mcp__github__create_pr` and `mcp__github__list_repos`. ## 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 - close the issue only when: - the user explicitly requests it, OR - all work is completed and no further user interaction is needed - never close an issue if you (Claude) need to ask the user for more information or if work is incomplete