Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Building an AI Code Review Bot for GitLab

By Kokil Thapa | Last reviewed: August 2026

Automating code quality checks is essential when your team ships frequently but lacks dedicated review bandwidth. Building an AI code review bot for GitLab solves this by intercepting merge request events, analyzing diffs against custom or LLM-driven rules, and posting inline feedback directly on the changed lines. This guide covers the practical implementation using PHP 8.4, Laravel 12, and the GitLab REST API, based on patterns I use in production automation workflows.

How do you architect a GitLab AI code review bot?

A reliable bot is not a single script; it is an event-driven system that must handle asynchronous payloads securely. When setting up CI/CD pipelines, I treat the review bot as a distinct service rather than embedding it inside the pipeline runner itself. This separation ensures that long-running AI inference or complex static analysis does not block your deployment stages or consume expensive CI minutes.

The architecture follows a strict request-response cycle. GitLab acts as the event producer, your Laravel application serves as the processor, and the GitLab API acts as the feedback channel. You must design for idempotency because GitLab may retry webhooks if your server responds slowly or with a 5xx error. Without deduplication logic, your bot will post duplicate comments on every retry attempt, which destroys developer trust immediately.

GitLab MR EventWebhook POSTX-Gitlab-TokenLaravel Processor1. Validate Token2. Deduplicate ID3. Fetch Diff4. AI / Rule Analysis5. Format FeedbackGitLab APIDiscussions EndpointInline Note Creation
Core architecture for building an AI code review bot for GitLab showing the unidirectional data flow from webhook trigger to API feedback.

In practice, the "Processor" box in this diagram should be split into two parts for any non-trivial analysis: a synchronous webhook receiver that returns HTTP 200 immediately, and an asynchronous Laravel Job that performs the actual diff fetching and AI inference. If you try to do everything synchronously within the webhook request timeout window (usually 10–30 seconds), your bot will fail intermittently under load or when the AI provider experiences latency.

How do you configure GitLab webhooks securely for code review?

Security is the first failure point when building an AI code review bot for GitLab. Your webhook endpoint is publicly accessible, meaning anyone who discovers the URL could inject fake review requests or attempt to exfiltrate data by triggering analysis on malicious payloads. Never deploy a webhook without token validation and IP allowlisting.

Setting up the webhook in GitLab

Navigate to your project’s Settings → Webhooks. Configure the following parameters strictly:

  • URL: https://your-domain.com/api/gitlab/webhook/review
  • Secret token: Generate a cryptographically random string (minimum 32 characters). Store this in your Laravel .env as GITLAB_WEBHOOK_SECRET.
  • Trigger: Select only Merge request events. Do not enable push events unless you specifically want to review commits outside of MRs.
  • SSL verification: Always enable. If your SSL certificate is invalid, fix the certificate rather than disabling verification.

Validating the webhook signature in Laravel

Your controller must reject any request where the X-Gitlab-Token header does not match your stored secret. Use constant-time comparison to prevent timing attacks:

<?php

namespace App\Http\Controllers\Api;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use App\Jobs\ProcessCodeReviewJob;

class GitLabWebhookController extends Controller
{
    public function handleReview(Request $request)
    {
        $secret = config('services.gitlab.webhook_secret');
        $providedToken = $request->header('X-Gitlab-Token');

        if (!$providedToken || !hash_equals($secret, $providedToken)) {
            Log::warning('Invalid GitLab webhook token received', [
                'ip' => $request->ip(),
            ]);
            abort(401, 'Unauthorized');
        }

        // Return 200 immediately; process asynchronously
        ProcessCodeReviewJob::dispatch(
            $request->all()
        );

        return response()->json(['status' => 'queued'], 200);
    }
}

This pattern ensures your webhook endpoint remains responsive even if the downstream AI service takes 45 seconds to respond. The job queue handles retries independently, and GitLab sees a successful delivery.

How do you fetch and parse merge request diffs programmatically?

The webhook payload contains metadata about the merge request but rarely includes the full diff content needed for line-level analysis. When building an AI code review bot for GitLab, you must make a secondary API call to retrieve the changeset. This is where most implementations break due to pagination or incorrect position mapping.

Retrieving the MR changes

Use the GitLab API v4 endpoint GET /projects/:id/merge_requests/:mr_iid/changes. In Laravel, use the HTTP client with proper authentication:

$response = Http::withToken(config('services.gitlab.token'))
    ->get("{$baseUrl}/api/v4/projects/{$projectId}/merge_requests/{$mrIid}/changes");

if ($response->failed()) {
    Log::error('Failed to fetch MR changes', [
        'project' => $projectId,
        'mr' => $mrIid,
        'status' => $response->status(),
    ]);
    return;
}

$changes = $response->json('changes');

Understanding the position object

To post an inline comment, you cannot simply reference a line number. GitLab requires a position object that ties your comment to a specific blob hash and line coordinate. Each file in the changes array includes this structure:

  • old_path / new_path: File paths before and after the change.
  • old_line / new_line: The line numbers in the respective versions. For added lines, old_line is null. For deleted lines, new_line is null.
  • a_mode / b_mode: File permission modes.

You must preserve these exact values when constructing your discussion payload. Modifying them causes the API to reject the comment with a 400 error because the position no longer maps to a valid diff hunk.

Laravel JobGitLab APIAI ServiceGET /changesDiff + PositionSend Diff ContextStructured FindingsPOST /discussions201 Created
Sequence flow for building an AI code review bot for GitLab demonstrating the required secondary API calls for diff retrieval and feedback posting.

How do you post inline comments using the GitLab Discussions API?

Posting general notes on a merge request is trivial, but inline comments require precise payload construction. The endpoint is POST /projects/:id/merge_requests/:mr_iid/discussions. When building an AI code review bot for GitLab, this is the step where most developers encounter silent failures because the position hash does not match the current diff state.

Constructing the discussion payload

Each finding from your analysis must be mapped to a valid position object extracted directly from the /changes response. Do not reconstruct positions manually:

$payload = [
    'body' => $finding['message'],
    'position' => [
        'base_sha' => $mrDetails['diff_refs']['base_sha'],
        'start_sha' => $mrDetails['diff_refs']['start_sha'],
        'head_sha' => $mrDetails['diff_refs']['head_sha'],
        'position_type' => 'text',
        'new_path' => $file['new_path'],
        'new_line' => $finding['line_number'],
    ],
];

$response = Http::withToken(config('services.gitlab.token'))
    ->post(
        "{$baseUrl}/api/v4/projects/{$projectId}/merge_requests/{$mrIid}/discussions",
        $payload
    );

The three SHA values (base_sha, start_sha, head_sha) are critical. They anchor your comment to a specific version of the diff. If the author pushes new commits after your bot starts processing but before it posts, these SHAs may become stale. Always fetch fresh diff_refs immediately before posting, or handle the 409 Conflict response gracefully by re-fetching and retrying.

Handling rate limits and batching

GitLab enforces API rate limits that vary by tier. On self-managed instances, administrators can adjust these, but on GitLab.com SaaS, you are typically limited to 2,000 requests per minute for authenticated users. If your bot analyzes large MRs with dozens of findings, batch your comments or introduce delays between posts. A common pattern I use on Laravel API projects is to dispatch individual jobs for each comment with a chained delay, rather than looping through all findings in a single job execution.

What are the best practices for AI code review accuracy and cost control?

Sending entire files to an LLM is expensive and produces noisy reviews. When building an AI code review bot for GitLab for clients in Nepal and globally, I have found that context window management matters more than model selection. A focused prompt with relevant diff hunks outperforms a vague prompt with full-file context every time.

StrategyCost ImpactAccuracyImplementation Complexity
Full-file analysisHigh (tokens scale with file size)Low (lost in noise)Low
Diff-only analysisMedium (only changed lines)Medium (missing context)Low
Diff + targeted context retrievalOptimized (relevant tokens only)HighHigh
Hybrid static analysis + AILowest (AI only for ambiguous cases)HighestHighest

Implementing the hybrid approach

Run deterministic static analysis tools (PHPStan, ESLint, Rector) first. These catch syntax errors, type mismatches, and style violations at near-zero cost. Only escalate to the AI for issues that require semantic understanding: business logic correctness, potential security vulnerabilities in dynamic queries, or architectural inconsistencies. This reduces your AI token spend by 60–80% on typical Laravel projects while improving signal-to-noise ratio.

Prompt engineering for code review

Your system prompt should include the project’s coding standards, framework version constraints, and domain-specific rules. For legal-tech portals I maintain, the prompt explicitly instructs the AI to flag any user input that bypasses validation before reaching Eloquent models, because document integrity is non-negotiable in that domain. Generic prompts produce generic advice; constrained prompts produce actionable reviews.

New MR Diff ReceivedStatic Analysis GateDeterministic Issues Found?YESNOPost Static Findings(Zero AI Cost)Escalate to AI Model(Semantic Review Only)
Hybrid decision flow for building an AI code review bot for GitLab that gates AI usage behind deterministic static analysis to reduce cost and improve accuracy.

How do you handle idempotency and prevent duplicate bot comments?

Webhooks are inherently unreliable. Network blips, server restarts during deployment, or GitLab’s own retry logic can deliver the same MR event multiple times. Without idempotency guards, your bot will post identical reviews repeatedly, which annoys developers and wastes API quota.

Implement a deduplication layer using Redis or your database. Create a composite key from the project ID, MR IID, and the latest commit SHA. Before processing, check if this key exists with a TTL matching your expected maximum processing window plus buffer:

$deduplicationKey = "gitlab:review:{$projectId}:{$mrIid}:{$headSha}";

if (!Cache::add($deduplicationKey, true, now()->addHours(2))) {
    Log::info('Duplicate webhook skipped', [
        'project' => $projectId,
        'mr' => $mrIid,
        'sha' => $headSha,
    ]);
    return;
}

The Cache::add() method is atomic—it only succeeds if the key does not already exist. This prevents race conditions where two concurrent webhook deliveries both pass a non-atomic check-then-set pattern. For teams working across time zones like Kathmandu and international clients, this reliability is what separates a useful tool from one that gets disabled after a week.

Conclusion

Building an AI code review bot for GitLab is fundamentally an integration engineering problem, not an AI problem. The model is a component; the reliability comes from secure webhook handling, correct position mapping, idempotent processing, and intelligent context selection. Start with the hybrid static-analysis-first approach to validate your pipeline mechanics before adding LLM costs. Test thoroughly on a sandbox project with synthetic merge requests before enabling on production repositories.

If you need help implementing automated code review systems, DevOps automation, or integrating AI tooling into existing Laravel workflows, reach out to discuss your specific requirements. I have built and maintained these systems in production for legal-tech platforms and eCommerce applications where accuracy and reliability are non-negotiable.

Frequently Asked Questions

The bot requires a Project Access Token with read_repository and write_merge_request scopes. Never grant maintainer or admin privileges. In my experience deploying these on client GitLab instances, limiting scope to merge request comments prevents accidental branch deletions or setting modifications while allowing the bot to post inline feedback safely.

Expect USD 20–50 (NPR 2,700–6,700) monthly for API tokens on active repositories. Costs depend entirely on commit volume and model choice. GPT-4o-mini reviews cost fractions of a cent each, while Opus or o1 models can push bills to USD 100+ for high-frequency teams. Monitor usage via provider dashboards weekly.

Yes. Ollama or vLLM running Llama-3-70B-Instruct on a local GPU server eliminates per-token fees. I have configured this for Nepal-based legal-tech clients requiring data sovereignty. You need at least 48GB VRAM for competent coding models. Trade higher hardware costs against zero API latency and complete privacy control.

Use project access tokens scoped only to specific repositories, never personal tokens. Configure your .gitlab-ci.yml to pass only the diff content, not full file history, to the API. For sensitive projects like legal portals I build, I route requests through a proxy that strips metadata and enforces allowlists before reaching external LLM endpoints.

Claude 3.5 Sonnet and GPT-4o currently outperform others on Laravel 12 and PHP 8.4 syntax. They understand service containers, Eloquent relationships, and Spatie packages accurately. In my production experience, generic models often hallucinate deprecated Laravel facades. Always test against your actual codebase patterns before committing to a provider for framework-specific reviews.

Implement a confidence threshold in your prompt engineering, instructing the model to flag issues only above 80% certainty. Add a .ai-review-ignore file listing known exceptions. On real projects, I found that without explicit suppression rules, developers stop trusting the bot within weeks due to noise. Feedback loops where devs mark unhelpful comments improve accuracy over time.

Yes. Configure it as a pipeline job triggered on merge_request_event. The job fetches the diff via CI variables, sends it to the LLM, then posts results using the GitLab API. I use Deployer 7 and GitLab CI for sister sites like notarykathmandu.com, and this pattern integrates cleanly without blocking merges unless you explicitly add fail conditions.

Most LLMs cap context at 128k tokens, but large diffs exceed this. Split reviews by file or chunk changes into logical units under 30k tokens. Send only changed lines plus minimal surrounding context. On a recent Laravel eCommerce project, sending entire controllers caused truncated responses. Chunking improved review quality and reduced API costs significantly.

Store keys as masked, protected CI/CD variables restricted to protected branches. Never hardcode in .gitlab-ci.yml or commit .env files. Rotate tokens quarterly. For client projects, I use separate tokens per environment so a compromised staging key cannot access production repositories. Audit variable access logs regularly through GitLab’s audit events feature.

Technically yes, but I strongly advise against it. AI should suggest, humans should approve. Auto-approval creates liability gaps, especially in regulated domains like legal-tech or payments. Instead, configure the bot to add labels like ai-reviewed or needs-human-check. Let maintainers make final decisions. Trust must be earned through consistent accuracy over months, not forced via automation.

Under 60 seconds for typical merge requests. If reviews exceed two minutes, your diff is too large or API latency is high. Use asynchronous webhooks for massive MRs instead of blocking pipelines. On production deployments I manage, slow reviews frustrate developers and get disabled. Optimize prompts, cache repeated analyses, and parallelize file-level checks to maintain fast feedback cycles.

System prompt defines role as senior PHP/Laravel engineer. User prompt includes diff, coding standards link, and specific focus areas like security or performance. Explicitly request line-number references and actionable fixes. Vague prompts yield vague feedback. I iterate prompts based on developer complaints. After three refinements on a booking system project, useful comment rates jumped from 30% to 75%.

Run PHPStan, ESLint, and Pint first in pipeline. Only send clean diffs to AI for architectural and logic review. This avoids wasting tokens on formatting issues static analysis catches cheaper and faster. On WooCommerce and Laravel projects, this layered approach reduced AI costs by 40% while improving signal-to-noise ratio. Treat AI as complement, not replacement, for deterministic tooling.

Yes, but configure language detection per file extension. Send PHP files to models strong in Laravel, JavaScript to those proficient in Vue or Alpine. Generic prompts across mixed stacks produce mediocre results. In polyglot projects I maintain, routing files to specialized prompts improved relevance dramatically. Maintain separate system prompts per language and select dynamically during pipeline execution based on changed file types.

Track metrics: review comments accepted versus dismissed, time-to-merge before and after adoption, and bug escape rate. Survey developers monthly on perceived usefulness. On a legal services portal, we saw 20% faster merges and caught three critical authorization bugs in month one. Without measurement, you cannot justify ongoing costs or distinguish signal from novelty. Quantify impact or retire the tool.

Share this article

Quick Contact Options
Choose how you want to connect me: