Skip to content

peteroden/gitlab-copilot-agent

Repository files navigation

GitLab Copilot Agent

License: MIT

Automated code review for GitLab Merge Requests, powered by the GitHub Copilot SDK.

What It Does

  • Webhook-driven reviews: GitLab MR webhooks trigger Copilot-powered code review with inline suggestions
  • GitLab polling (GITLAB_POLL=true): Polls GitLab API for open MRs and /copilot <instruction> comments — no webhook required
  • Jira integration: Polls Jira for issues in a trigger status, creates branches + MRs in GitLab, triggers agent review
  • /copilot commands: MR comments starting with /copilot trigger the agent with custom instructions (e.g., /copilot add unit tests)
  • Repo-level configuration: Loads project-specific skills, agents, and instructions from .github/, .claude/, AGENTS.md
  • Task execution: Local subprocess or Kubernetes Job isolation per review
  • K8s executor: Can run review jobs as Kubernetes Jobs instead of local processes
  • OTEL observability: Full OpenTelemetry traces, metrics, and logs when endpoint is configured

📖 Developer Wiki — comprehensive architecture docs, module reference, security model, deployment guides, and more.

Quick Start

Docker (simplest)

docker build -t gitlab-copilot-agent .
docker run -p 8000:8000 \
  -e GITLAB_URL=https://gitlab.com \
  -e GITLAB_TOKEN=glpat-... \
  -e GITLAB_WEBHOOK_SECRET=secret \
  -e GITHUB_TOKEN=gho_... \
  gitlab-copilot-agent

k3d (full stack with Helm)

# 1. Start devcontainer
devcontainer up --workspace-folder .

# 2. Configure environment
cp .env.k3d.example .env.k3d   # fill in real values

# 3. Deploy to k3d
make k3d-up                     # create k3d cluster
make k3d-build                  # build & import image
make k3d-deploy                 # deploy via Helm

Environment Variables

GitLab (required)

Variable Default Description
GITLAB_URL GitLab instance URL
GITLAB_TOKEN GitLab API private token (needs api scope)
GITLAB_WEBHOOK_SECRET Secret for validating webhook payloads

Auth (one required)

Variable Default Description
GITHUB_TOKEN GitHub token for Copilot auth
COPILOT_PROVIDER_TYPE None BYOK provider: azure, openai, or omit for Copilot
COPILOT_PROVIDER_BASE_URL None BYOK provider endpoint
COPILOT_PROVIDER_API_KEY None BYOK provider API key

Model

Variable Default Description
COPILOT_MODEL gpt-4 Model for reviews

Server

Variable Default Description
HOST 0.0.0.0 Server bind host
PORT 8000 Server bind port
LOG_LEVEL info Log level
AGENT_GITLAB_USERNAME None Agent's GitLab username for loop prevention
CLONE_DIR system temp Base directory for repo clones

GitLab Polling

Variable Default Description
GITLAB_POLL false Enable GitLab API polling for MR/note discovery
GITLAB_POLL_INTERVAL 30 Polling interval in seconds
GITLAB_POLL_LOOKBACK 60 Minutes to look back on startup for recent MRs
GITLAB_REVIEW_ON_PUSH true Re-review MRs on each new commit. Set false to review once per MR
GITLAB_PROJECTS None Comma-separated project paths/IDs (required when polling)

Task Execution

Variable Default Description
TASK_EXECUTOR local local, kubernetes, or container_apps
DISPATCH_BACKEND azure_storage Dispatch backend: azure_storage (Queue + Blob via Claim Check)
K8S_NAMESPACE default Kubernetes namespace for Jobs
K8S_JOB_IMAGE Docker image for Job pods
K8S_JOB_CPU_LIMIT 1 CPU limit
K8S_JOB_MEMORY_LIMIT 1Gi Memory limit
K8S_JOB_TIMEOUT 600 Job timeout in seconds
K8S_SECRET_NAME None K8s Secret for Job pod credentials (auto-set by Helm)
K8S_CONFIGMAP_NAME None K8s ConfigMap for Job pod config (auto-set by Helm)
K8S_JOB_INSTANCE_LABEL "" Helm release label for NetworkPolicy scoping (auto-set by Helm)

Azure Storage (Dispatch)

Variable Default Description
AZURE_STORAGE_CONNECTION_STRING None Azure Storage connection string (for Azurite/K8s). Overrides URL-based auth.
AZURE_STORAGE_ACCOUNT_URL None Azure Blob Storage endpoint for managed identity auth (ACA).
AZURE_STORAGE_QUEUE_URL None Azure Queue Storage endpoint for managed identity auth (ACA).
TASK_QUEUE_NAME task-queue Azure Storage Queue name for task dispatch
TASK_BLOB_CONTAINER task-data Azure Blob container for params and results

Jira (all optional)

Variable Default Description
JIRA_URL None Jira instance URL
JIRA_EMAIL None Jira user email
JIRA_API_TOKEN None Jira API token / PAT
JIRA_TRIGGER_STATUS AI Ready Status that triggers agent
JIRA_IN_PROGRESS_STATUS In Progress Status after pickup
JIRA_POLL_INTERVAL 30 Poll interval seconds
JIRA_PROJECT_MAP None JSON mapping Jira project keys to GitLab projects

Observability

Variable Default Description
OTEL_EXPORTER_OTLP_ENDPOINT None OTLP endpoint — enables traces, metrics, logs export
OTEL_EXPORTER_OTLP_PROTOCOL grpc Protocol: grpc (port 4317) or http/protobuf (port 4318)
OTEL_SERVICE_NAME gitlab-copilot-agent Service name in OTEL resource attributes
DEPLOYMENT_ENV Deployment environment label (e.g., production, staging)
SERVICE_VERSION 0.1.0 Service version in OTEL resource attributes

GitLab Webhook Setup

  1. Go to your GitLab project → SettingsWebhooks
  2. Set the URL to https://your-host/webhook
  3. Set the secret token to match GITLAB_WEBHOOK_SECRET
  4. Check Merge request events
  5. Save

The service needs a publicly reachable URL. For local dev, use ngrok: ngrok http 8000.

Jira Integration

The service can optionally poll Jira for issues and automatically create branches + MRs for agent review. See the Jira section in Environment Variables above for configuration.

Example JIRA_PROJECT_MAP

The JSON must use the {"mappings": {...}} schema with numeric gitlab_project_id and clone_url:

{
  "mappings": {
    "PROJ": {
      "gitlab_project_id": 42,
      "clone_url": "https://gitlab.com/myorg/myrepo.git",
      "target_branch": "main"
    },
    "DEMO": {
      "gitlab_project_id": 87,
      "clone_url": "https://gitlab.com/demos/example.git",
      "target_branch": "develop"
    }
  }
}

How It Works

  1. Polls JQL: Every JIRA_POLL_INTERVAL seconds, queries Jira for issues in JIRA_TRIGGER_STATUS
  2. Transitions: Moves picked-up issues to JIRA_IN_PROGRESS_STATUS
  3. Creates branches: Creates a new branch from target_branch (named {project-key}-{issue-number}-{sanitized-title})
  4. Creates MRs: Opens an MR from the new branch, targeting target_branch
  5. Triggers review: The MR triggers the normal webhook review flow

Repo-Level Configuration

The agent automatically loads project-specific config from the reviewed repo:

Skills and agents (from .github/ and .claude/):

Path What How
.github/skills/*/SKILL.md Skills SDK-native skill_directories
.claude/skills/*/SKILL.md Skills SDK-native skill_directories
.github/agents/*.agent.md Custom agents SDK-native custom_agents (YAML frontmatter)
.claude/agents/*.agent.md Custom agents SDK-native custom_agents (YAML frontmatter)

Instructions (all discovered, concatenated into system message):

Path Standard Scope
.github/copilot-instructions.md GitHub Copilot Project-wide
.github/instructions/*.instructions.md GitHub Copilot Per-language
.claude/CLAUDE.md Claude Code Project-wide
AGENTS.md Universal (Copilot, Claude, Codex, Cursor, GitLab Duo) Project root + subdirectories
CLAUDE.md Claude Code Project root

Symlinked files (e.g., ln -s AGENTS.md CLAUDE.md) are deduplicated automatically.

Review Output

Each review comment includes:

  • Severity tag: [ERROR], [WARNING], or [INFO]
  • Description of the issue
  • Inline code suggestion (when a concrete fix exists) — click "Apply suggestion" in the GitLab UI to commit the fix

Operations

Memory Bounds

The service uses in-memory structures that are bounded to prevent growth during long uptimes:

Structure Purpose Default Limit Eviction Strategy
RepoLockManager Serializes concurrent operations on the same repo 1,024 entries LRU — evicts oldest idle (unlocked) lock
ProcessedIssueTracker Prevents re-processing Jira issues within a run 10,000 entries Drops oldest 50% when limit is reached

Active locks are never evicted — the lock manager allows temporary over-capacity rather than dropping in-use locks. Both limits are configurable via constructor arguments but not currently exposed as environment variables.

Task Execution

The service supports three execution backends controlled by TASK_EXECUTOR:

Backend How It Works Use Case
local (default) Runs Copilot CLI as a local subprocess Development, single-node
kubernetes Enqueues to Azure Storage Queue; KEDA ScaledJob triggers task runner pods Production, self-hosted K8s
container_apps Enqueues to Azure Storage Queue; KEDA event trigger starts ACA Job executions Production, Azure-managed

All remote executors use the same Claim Check dispatch pattern: params blob + queue message → KEDA triggers job → task runner dequeues, executes, writes result blob → controller polls result.

Coding task reliability:

  • Agent outputs structured JSON listing files intentionally changed (prevents accidental artifact commits)
  • In-session retry if agent doesn't return required format
  • Branch name collision detection appends -2, -3, etc. on retry
  • Stale completed K8s Jobs are automatically replaced on re-execution

Troubleshooting

Common Issues

Webhook not triggering

  • Check that the webhook URL is publicly reachable (test with curl https://your-host/webhook)
  • Verify GITLAB_WEBHOOK_SECRET matches the secret configured in GitLab
  • Ensure "Merge request events" is enabled in the webhook settings
  • Check the GitLab webhook event log (Settings → Webhooks → Recent Deliveries)

Review posts no inline comments (only a summary)

  • Check logs for diff position validation errors — GitLab rejects positions that don't match the diff
  • Ensure the MR has actual file changes (empty MRs won't have reviewable diffs)
  • Verify the agent is analyzing the correct commit range

Jira poller not processing issues

  • Verify JIRA_PROJECT_MAP is valid JSON and contains the Jira project key
  • Check that issues are in the exact status name from JIRA_TRIGGER_STATUS (case-sensitive)
  • Confirm the Jira user has permission to query and transition issues
  • Check logs for JQL query errors or API authentication failures

Git clone timeout or authentication failures

  • Verify GITLAB_TOKEN has api scope and read access to the target project
  • Check network connectivity from the container to the GitLab instance
  • Ensure the GitLab project URL is valid and accessible
  • For self-hosted GitLab, verify SSL certificates are trusted

Debug Logging

Enable detailed debug logs:

export LOG_LEVEL=debug

This shows:

  • Full webhook payloads
  • Git clone/checkout commands
  • Copilot SDK interactions
  • Diff position calculations
  • API request/response details

Development

# Install pre-commit hook (runs ruff + mypy before each commit)
ln -sf ../../scripts/pre-commit .git/hooks/pre-commit

# Run tests
devcontainer exec --workspace-folder . uv run pytest

# Lint
devcontainer exec --workspace-folder . uv run ruff check src/ tests/

# Type check
devcontainer exec --workspace-folder . uv run mypy src/

E2E Tests

End-to-end tests deploy the agent to k3d and test three flows against host-side mock services. Prerequisites: Docker, Make, k3d, kubectl.

  1. Webhook MR review — sends MR webhook → verifies review comments posted
  2. Jira polling — agent polls mock Jira for "AI Ready" issues → verifies transitions, MR creation, comments
  3. /copilot command — sends note webhook with /copilot <instruction> → verifies agent response comment
# Create the E2E cluster
make e2e-up

# Run the full E2E test (builds, deploys, sends webhook, verifies comments)
make e2e-test

# Tear down
make e2e-down

Demo

See docs/DEMO.md for automated demo environment setup. One command provisions a GitLab repo + Jira project showcasing all agent capabilities.

License

This project is licensed under the MIT License.

Architecture

See the Developer Wiki for full architecture documentation, including:

GitLab Webhook/Poller → FastAPI → Clone repo → Copilot agent review → Parse output → Post inline comments + summary

Local Kubernetes Development

Run the full stack locally using k3d (k3s-in-Docker). All tooling runs inside the devcontainer — no host-side k8s tools required.

Devcontainer Tooling

The devcontainer includes everything needed for local k8s development:

Tool Installed via Purpose
Docker docker-in-docker devcontainer feature Container runtime for k3d nodes
kubectl kubectl-helm-minikube devcontainer feature Cluster interaction
Helm 3 kubectl-helm-minikube devcontainer feature Chart deployment
k3d v5.7.5 postCreateCommand install script Local k3s cluster management

Docker-in-Docker (not Docker-outside-of-Docker) is required because k3d creates its own Docker containers, networks, and port bindings. DooD would cause path and networking mismatches between the devcontainer and host.

Dev Flow

┌─────────────────────────────────────────────────┐
│                 Devcontainer                     │
│                                                  │
│  1. Start devcontainer (tools auto-installed)    │
│  2. make k3d-up       → k3d cluster (~30s)       │
│  3. make k3d-build     → docker build + import    │
│  4. make k3d-deploy    → helm install             │
│  5. Edit code → make k3d-redeploy (iterate)       │
│  6. make k3d-down      → teardown when done       │
│                                                  │
│  Docker-in-Docker daemon (persists across sleep)  │
│  └── k3d cluster (k3s nodes as containers)        │
│      └── Controller pod + Azurite pod + KEDA ScaledJob pods │
└─────────────────────────────────────────────────┘

Cluster lifecycle:

  • make k3d-up creates a fresh cluster (~30-40s first time).
  • Devcontainer sleep/restart: cluster recovers in ~5-10s (DinD volume persists).
  • Devcontainer rebuild: full cold start (DinD volume destroyed, re-run make k3d-up).

Quick Start

cp .env.k3d.example .env.k3d   # fill in real values
make k3d-up                     # create k3d cluster
make k3d-build                  # build & import image
make k3d-deploy                 # deploy via Helm

Commands

Command Description
make k3d-up Create k3d cluster
make k3d-down Delete k3d cluster
make k3d-build Build image & import into cluster
make k3d-deploy Deploy/upgrade via Helm
make k3d-redeploy Rebuild + redeploy (one step)
make k3d-logs Tail controller logs
make k3d-status Show pods, jobs, and services

Webhook Testing

The controller is exposed on localhost:8080 via k3d port mapping + ServiceLB (override with K3D_HOST_PORT=9000 make k3d-up). curl http://localhost:8080/health works immediately after deploy — no kubectl port-forward needed.

For manual port-forward (if not using ServiceLB):

kubectl port-forward svc/copilot-agent 8000:8000

Live E2E Verification

After deploying to the k3d cluster, verify the full system end-to-end:

# 1. Check all pods are running
make k3d-status

# 2. Verify webhook endpoint responds
curl -s http://localhost:8080/health

# 3. Send a test webhook payload (dry run)
curl -X POST http://localhost:8080/webhook \
  -H "Content-Type: application/json" \
  -H "X-Gitlab-Token: $(grep GITLAB_WEBHOOK_SECRET .env.k3d | cut -d= -f2)" \
  -d '{"object_kind": "merge_request", "event_type": "merge_request"}'

# 4. Watch logs during a live MR event
make k3d-logs

Full live E2E (requires GitLab + ngrok/tunnel):

  1. Start tunnel: ngrok http 8080 (or use VS Code port forwarding)
  2. Configure webhook in GitLab project pointing to tunnel URL
  3. Open/update an MR → observe agent review in make k3d-logs
  4. Verify inline comments appear on the MR

Jira live E2E (requires Jira credentials in .env.k3d):

  1. Transition a Jira issue to the trigger status
  2. Watch make k3d-logs for poller pickup
  3. Verify branch + MR creation in GitLab
  4. Verify agent self-review on the new MR

About

Automated GitLab MR code review powered by GitHub Copilot SDK

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors