This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
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.
The codebase follows a clean separation of concerns with seven main modules:
index.js (Entry Point)
├── server.js (HTTP Server)
│ └── webhookHandler.js (Event Management)
├── jobQueue.js (Job Queue & Persistence)
│ └── commandExecutor.js (Command Execution)
├── emailNotifier.js (Email Notifications)
│ └── emailSender.js (SMTP Email Sender)
├── config.js (Configuration)
└── logger.js (Logging)
WebhookServer (src/server.js)
http and crypto modulesPOST /webhook, GET /healthgetHandler() to access WebhookHandler for callback registrationWebhookHandler (src/webhookHandler.js)
handler.on(eventType, callback) - Event-specific callbackshandler.onAny(callback) - Global callbacks for all events(payload, headers) or (eventType, payload, headers) for globalJobQueue (src/jobQueue.js)
queue.json for crash recoveryQUEUE_PARALLEL_PER_REPOSITORY environment variableCommandExecutor (src/commandExecutor.js)
{{variable}} syntax/bin/bash with timeout protection{{repo}}, {{branch}}, {{pusher}}, {{commit}}, {{pr_number}}, {{issue_number}}, {{issue_title}}, etc.COMMAND_TIMEOUTConfig (src/config.js)
.env file synchronously on startupprocess.env (env vars take precedence)get(), getBoolean(), getNumber()Logger (src/logger.js)
info(), error(), webhook(), warn(), debug(), jobLifecycle()FILE_LOGGING_ENABLED environment variable/var/log/agent-manager.logMAX_LOG_SIZE (default: 10MB)MAX_LOG_FILES rotated files (default: 5)EmailNotifier (src/emailNotifier.js)
.smtp-config.json for SMTP server configurationemail-rules.json for notification rulesEmailSender (src/emailSender.js)
net and tls modulesindex.js loads config and creates WebhookServerqueue.json (if exists)WEBHOOK_PUSH_ENABLED=true, etc.)npm start # Start production server
npm run dev # Start with auto-reload (--watch flag)
node examples/with-callbacks.js # Run callback example
The agent-manager service runs as a systemd service on the production system.
Restart the service:
sudo /bin/systemctl restart agent-manager
Check service status:
sudo /bin/systemctl status agent-manager
View service logs:
sudo journalctl -u agent-manager -f
View application log file:
tail -f /var/log/agent-manager.log
Debugging: When running as a systemd service, file logging is enabled by default. All application logs (INFO, WARN, ERROR, DEBUG, webhook events, job lifecycle) are written to /var/log/agent-manager.log. The log file is automatically rotated when it reaches 10MB (default), keeping up to 5 rotated files. For troubleshooting issues, check this file for detailed execution logs.
Note: The claude user has passwordless sudo access to restart the agent-manager service via /etc/sudoers.d/50-agent-manager configuration.
Testing endpoints:
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 is split between two files:
.env)Copy from .env.example and configure:
Server Configuration:
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:
WEBHOOK_{EVENT_TYPE}_ENABLED=true|false
Supported Event Types: push, pull_request, create, delete, release, issues, issue_comment, global
File Logging Configuration:
FILE_LOGGING_ENABLED=true|false # Enable/disable file logging (default: false, auto-enabled for systemd)
LOG_FILE_PATH=/var/log/agent-manager.log # Path to log file (default: /var/log/agent-manager.log)
MAX_LOG_SIZE=10485760 # Max size in bytes before rotation (default: 10MB)
MAX_LOG_FILES=5 # Number of rotated files to keep (default: 5)
Note: File logging is automatically enabled when running as a systemd service. The logger detects systemd environment and enables file output by default. For debugging, check /var/log/agent-manager.log.
MCP Server Configuration:
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.
commands.json)Copy from commands.json.example and configure commands for each event type.
Command Structure:
{
"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/scriptnode - Execute Node.js script (automatically prepends node command)Per-Command Settings:
name - Unique identifier for the commanddescription - Human-readable descriptiontype - Execution type (shell or node)command - Command or script pathargs - Array of arguments (supports variable substitution)cwd - Working directory (null = project root)timeout - Timeout in millisecondsfilterBranch - 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 actionsfilterAssignee - Only execute when issue/PR is assigned to specific user (string, optional). If omitted, execute regardless of assigneeimport/export syntax ("type": "module" in package.json)server.stop() before exit.env for enable/disable flagscommands.json for command definitions.smtp-config.json for SMTP server configurationemail-rules.json for email notification rulesfilterBranch - Execute only for specific branchesfilterActions - Execute only for specific actions (e.g., ["assigned", "opened"])filterAssignee - Execute only when assigned to specific user (e.g., "claude")The server supports custom callback registration beyond .env command execution:
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.
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){{issue_labels}} - Comma-separated list of issue label names (issue/issue_comment events){{comment_body}} - Comment text (issue_comment events)Variables are extracted in commandExecutor.js via extractVariables() and substituted via substituteVariables().
The webhook server uses a persistent job queue to ensure reliable command execution. The queue system supports two operating modes:
Single-Queue Mode (Default)
Multi-Queue Mode (Parallel Per Repository)
QUEUE_PARALLEL_PER_REPOSITORY=truequeue.json after every changeSingle-Queue Mode:
{
"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"
}
Multi-Queue Mode:
{
"queues": {
"owner/repo1": {
"queue": [
{
"id": 1,
"eventType": "push",
"status": "pending",
"createdAt": "2025-10-28T12:00:00.000Z",
"config": { "command": "...", "enabled": true }
}
]
},
"owner/repo2": {
"queue": [
{
"id": 2,
"eventType": "issues",
"status": "running",
"createdAt": "2025-10-28T12:01:00.000Z",
"config": { "command": "...", "enabled": true }
}
]
}
},
"jobIdCounter": 5,
"lastSaved": "2025-10-28T12:05:00.000Z"
}
Single-Queue Mode:
Multi-Queue Mode:
The webhook server includes an email notification system that sends emails based on webhook events and configurable conditions. The system follows the same callback pattern as the Claude agent client.
EmailNotifier (src/emailNotifier.js)
.smtp-config.json and email-rules.jsonEmailSender (src/emailSender.js)
net and tls modules.smtp-config.json - SMTP server configuration (excluded from git):
{
"host": "mail.example.com",
"port": 1025,
"secure": false,
"user": "your-email@example.com",
"pass": "your-password",
"from": "noreply@example.com"
}
email-rules.json - Email notification rules (excluded from git):
{
"rules": [
{
"name": "mention-notification",
"event": "issue_comment",
"condition": {
"type": "mention",
"value": "username"
},
"recipients": ["{{mentioned_user}}"],
"subject": "You were mentioned in {{full_repo}}#{{issue_number}}",
"body": "Comment by {{sender}}:\n{{comment_body}}"
}
]
}
{{mentioned_user}} - User mentioned in comment/issue (extracts email from payload){{sender}} - Event sender email{{assignee}} - Issue/PR assignee email"admin@example.com")All standard webhook variables are available in email subjects and bodies:
{{repo}}, {{full_repo}}, {{repo_owner}}{{branch}}, {{commit}}, {{commit_msg}}{{sender}}, {{mentioned_user}}{{issue_number}}, {{issue_title}}, {{issue_action}}, {{pr_number}}, {{pr_action}}{{comment_body}}, {{issue_body}}The email notifier is automatically integrated in src/index.js:
import emailNotifier from './emailNotifier.js';
// ... after handler setup ...
emailNotifier.register(handler);
If .smtp-config.json is missing, email notifications are disabled automatically.
.gitignore)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 clientsrc/config.ts - Configuration managementsrc/repository-manager.ts - Git repository operations (clone, pull, environment setup)src/gogs-helper.ts - Gogs API helper functionssrc/types.ts - TypeScript type definitionssrc/index.ts - CLI entry pointdist/ - Compiled JavaScript (auto-generated by npm run build)Usage:
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:
.envThe Claude agent automatically adjusts its behavior based on issue labels:
🔴 "question" label:
📚 "documentation" label:
🐛 "bug" label:
✨ "enhancement" label:
Labels are automatically extracted from webhook payloads and passed to the client as the {{issue_labels}} variable.
See client/README.md for detailed documentation.
The client supports flexible MCP server configuration with three priority levels:
~/.config/agent-manager/mcp/{owner}/{repo}.json~/.config/agent-manager/mcp/default.jsonKey Features:
${VAR_NAME} syntaxConfiguration Structure:
{
"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.
The client automatically discovers available tools from all configured MCP servers at runtime:
How It Works:
ToolDiscovery class connects to each configured MCP servertools/list methodmcp__{server-name}__{tool-name}allowedToolsBenefits:
Built-in Tools (always available):
Read, Write, Edit - File operationsBash, Grep, Glob - Shell operationsTodoWrite, Task - Task managementWebFetch, WebSearch - Web accessNotebookEdit, BashOutput, KillShell - Advanced operationsSkill, SlashCommand - Extension pointsExample: 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.
Main Server (src/):
.env.example when adding new configuration optionsexport default new ClassName()No formal test suite currently. Test manually using:
npm run dev