<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Manikanta Suru</title>
    <description>The latest articles on DEV Community by Manikanta Suru (@manikanta_suru_92).</description>
    <link>https://dev.to/manikanta_suru_92</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3925476%2Fde45b7fa-a762-41fd-92db-e75b49aed84b.png</url>
      <title>DEV Community: Manikanta Suru</title>
      <link>https://dev.to/manikanta_suru_92</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/manikanta_suru_92"/>
    <language>en</language>
    <item>
      <title>LLM Integration in CI/CD: Real Use Cases Beyond Code Completion</title>
      <dc:creator>Manikanta Suru</dc:creator>
      <pubDate>Mon, 29 Jun 2026 23:09:22 +0000</pubDate>
      <link>https://dev.to/manikanta_suru_92/llm-integration-in-cicd-real-use-cases-beyond-code-completion-e8f</link>
      <guid>https://dev.to/manikanta_suru_92/llm-integration-in-cicd-real-use-cases-beyond-code-completion-e8f</guid>
      <description>&lt;p&gt;A GitLab API token appeared in a README.md file. It was committed, pushed, and reviewed by two engineers before merging. Nobody caught it.&lt;/p&gt;

&lt;p&gt;This was not a junior developer's mistake. Both reviewers were experienced. The token was on line 47 of a 200-line file, sandwiched between installation instructions. At the end of a sprint, with three other reviews queued, it looked like documentation. It was a secret sitting in plain sight.&lt;/p&gt;

&lt;p&gt;We caught it the next day during a routine audit. By then it had been in the repository for 18 hours.&lt;/p&gt;

&lt;p&gt;Three weeks later, after integrating an LLM into our Jenkins MR validation pipeline, the same pattern — a token embedded in documentation — would have been flagged automatically, before any human reviewer opened the diff. CRITICAL severity. Specific line. Suggested fix.&lt;/p&gt;

&lt;p&gt;That single incident justified the integration. Everything else that followed confirmed it was the right architectural decision.&lt;/p&gt;

&lt;p&gt;This article is about what we built, what we learned, and — more importantly — where LLM integration in CI/CD actually works versus where it creates more problems than it solves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why CI/CD Is the Right Integration Point
&lt;/h2&gt;

&lt;p&gt;Before getting into specific use cases, it is worth being precise about why CI/CD pipelines are particularly well-suited for LLM integration.&lt;/p&gt;

&lt;p&gt;LLMs are pattern recognizers operating on text. CI/CD pipelines generate enormous volumes of structured and semi-structured text at predictable moments: code diffs at merge time, build logs on failure, infrastructure plans before apply, deployment events, test results. This text is already being produced — the question is whether anything useful can be done with it beyond passing it to the next pipeline stage.&lt;/p&gt;

&lt;p&gt;The answer, in specific cases, is yes. And the key word is specific.&lt;/p&gt;

&lt;p&gt;The mistake most teams make when exploring LLM integration in CI/CD is treating it as a general capability rather than a targeted one. LLMs are not a replacement for deterministic tools — linters, static analyzers, test runners — that already exist in most pipelines. They are useful precisely where those deterministic tools fall short: contextual pattern recognition, natural language generation, and cross-cutting analysis that does not fit neatly into a rules-based system.&lt;/p&gt;

&lt;p&gt;With that framing, here are five use cases that have worked in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Case 1: Automated MR/PR Code Review
&lt;/h2&gt;

&lt;p&gt;What it does: Fetches the merge request diff, sends changed files to an LLM with a structured review prompt, and posts findings as inline comments directly on the MR — before any human reviewer opens it.&lt;/p&gt;

&lt;p&gt;Implementation: A Jenkins pipeline triggers on every MR event via GitLab webhook. A Python script fetches the diff via GitLab API, sends each changed file to the LLM with file-type-aware prompts (separate prompts for Python, Terraform, JavaScript), and posts findings back to the MR via GitLab API.&lt;/p&gt;

&lt;p&gt;The pipeline stage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'AI Code Review'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;expression&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;gitlabActionType&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'MERGE'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
            &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;gitlabActionType&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'UPDATE'&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'''
            python3 scripts/claude_mr_review.py \
                --project-id ${gitlabMergeRequestTargetProjectId} \
                --mr-iid ${gitlabMergeRequestIid}
        '''&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;failure&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'AI review failed — pipeline continues'&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The post { failure } block is non-negotiable. The AI review stage must never block a merge due to an LLM API failure or timeout.&lt;/p&gt;

&lt;p&gt;What it catches at 100+ MRs per month:&lt;/p&gt;

&lt;p&gt;Hardcoded credentials — API keys, database passwords, tokens embedded in code or documentation. Terraform security misconfigurations — security groups open to 0.0.0.0/0, unencrypted RDS instances, IAM wildcard policies. Missing error handling in async functions. Sensitive data written to application logs.&lt;/p&gt;

&lt;p&gt;What prompt engineering actually required:&lt;/p&gt;

&lt;p&gt;The first version produced 30+ findings per MR with no severity differentiation. Developers stopped reading the reviews within a week — which is a worse outcome than having no AI review. Three changes fixed it.&lt;/p&gt;

&lt;p&gt;A hard maximum of 10 findings per review forces the model to prioritize. Mandatory severity classification (CRITICAL/HIGH/MEDIUM/LOW) lets developers triage without reading everything. File-type-specific prompts — Terraform reviews check different things than Python reviews — dramatically reduced irrelevant findings.&lt;/p&gt;

&lt;p&gt;Cost consideration: At 100+ MRs per month with multiple files per MR, model selection matters significantly. A lightweight, high-throughput optimized model is fast and cost-efficient at this volume. Using a more capable reasoning model for automated code review is cost-inefficient — the task does not require it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Case 2: Pipeline Failure Diagnosis
&lt;/h2&gt;

&lt;p&gt;What it does: When a CI/CD pipeline fails, the failure logs are sent to an LLM that returns a plain-English diagnosis and suggested remediation steps — structured as an addition to the pipeline failure notification.&lt;/p&gt;

&lt;p&gt;The problem it solves: Reading build logs is slow and requires context that shifts between engineers. A Kubernetes pod failure that surfaces as CrashLoopBackOff in the pipeline output requires pulling logs, reading describe output, and cross-referencing with recent changes. An experienced engineer can do this in 10–15 minutes. A less experienced engineer might take 45 minutes and still miss the root cause.&lt;/p&gt;

&lt;p&gt;An LLM can read the same log output and return a structured diagnosis in under 30 seconds. Not always correct. Correct often enough to be the first thing an engineer checks before starting manual investigation.&lt;/p&gt;

&lt;p&gt;What the output looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DIAGNOSIS: OOMKill — container exceeded memory limit

The pod was killed by the kubelet due to memory exhaustion.
Log shows heap usage reaching 512Mi before termination,
which matches the configured memory limit.

ROOT CAUSE: JVM default heap settings (-Xmx not set)
allow heap to grow to 25% of available memory. The container
limit of 512Mi is insufficient for the application workload.

SUGGESTED FIX:
1. Increase memory limit:
   kubectl set resources deployment api-server --limits=memory=1Gi -n production
2. Or set JVM heap explicitly: JAVA_OPTS="-Xmx400m -Xms256m"
3. Check other replicas — same configuration affects all pods.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Implementation pattern: A post-failure webhook triggers a script that extracts the last 200 lines of pipeline logs and the specific failure stage output, sends them to the LLM, and appends the diagnosis to the Slack failure notification. Engineers receive context with the alert, not separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Case 3: Infrastructure Plan Review
&lt;/h2&gt;

&lt;p&gt;What it does: Before terraform apply runs in the pipeline, the plan output is sent to an LLM for security and configuration review. Findings are posted as pipeline comments and optionally gate the apply stage for CRITICAL findings.&lt;/p&gt;

&lt;p&gt;Why this matters: Terraform plan review is where the tired engineer problem is most acute. By the time an infrastructure change reaches the apply stage in a pipeline, it has typically been reviewed once — often quickly, often by someone with limited Terraform context. Security misconfigurations that are obvious in isolation become invisible in a 200-line plan output.&lt;/p&gt;

&lt;p&gt;What it catches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;CRITICAL&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="nx"&gt;ingress&lt;/span&gt; &lt;span class="nx"&gt;rule&lt;/span&gt; &lt;span class="nx"&gt;allows&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="mi"&gt;5432&lt;/span&gt; &lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PostgreSQL&lt;/span&gt;&lt;span class="err"&gt;).&lt;/span&gt; &lt;span class="nx"&gt;Database&lt;/span&gt; &lt;span class="nx"&gt;should&lt;/span&gt; &lt;span class="nx"&gt;not&lt;/span&gt; &lt;span class="nx"&gt;be&lt;/span&gt; &lt;span class="nx"&gt;publicly&lt;/span&gt; &lt;span class="nx"&gt;accessible&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;
&lt;span class="nx"&gt;Fix&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;restrict&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;application&lt;/span&gt; &lt;span class="nx"&gt;security&lt;/span&gt; &lt;span class="nx"&gt;group&lt;/span&gt; &lt;span class="nx"&gt;ID&lt;/span&gt; &lt;span class="nx"&gt;only&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;

&lt;span class="nx"&gt;HIGH&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;aws_db_instance&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="nx"&gt;storage_encrypted&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;
&lt;span class="nx"&gt;RDS&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt; &lt;span class="nx"&gt;will&lt;/span&gt; &lt;span class="nx"&gt;be&lt;/span&gt; &lt;span class="nx"&gt;created&lt;/span&gt; &lt;span class="nx"&gt;with&lt;/span&gt; &lt;span class="nx"&gt;unencrypted&lt;/span&gt; &lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;
&lt;span class="nx"&gt;Fix&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="nx"&gt;storage_encrypted&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="nx"&gt;MEDIUM&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logs&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="nx"&gt;missing&lt;/span&gt; &lt;span class="nx"&gt;required&lt;/span&gt; &lt;span class="nx"&gt;tags&lt;/span&gt;
&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Environment&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Owner&lt;/span&gt;&lt;span class="err"&gt;).&lt;/span&gt; &lt;span class="nx"&gt;Compliance&lt;/span&gt; &lt;span class="nx"&gt;requirement&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gate vs inform: For CRITICAL findings, the pipeline can be configured to require manual approval before apply proceeds. For HIGH and below, findings are informational — the apply runs but the team is notified. This balance avoids blocking legitimate changes while ensuring CRITICAL security issues get human review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Case 4: Release Notes Generation
&lt;/h2&gt;

&lt;p&gt;What it does: After a release merge, commit messages, PR titles, and PR descriptions from the release branch are sent to an LLM that produces structured release notes in a consistent format.&lt;/p&gt;

&lt;p&gt;The toil it eliminates: Writing release notes is a task that happens at the end of a sprint when everyone is tired and focused on the next one. Output quality is inversely proportional to how close it is to the deadline. Notes are inconsistent across teams. Important changes get buried. Minor changes get inflated.&lt;/p&gt;

&lt;p&gt;An LLM generating release notes from structured commit data produces consistent output, categorizes changes correctly — features, fixes, breaking changes, infrastructure — and drafts the communication that goes to stakeholders.&lt;/p&gt;

&lt;p&gt;Important caveat: LLM-generated release notes require human review before publishing. The model cannot know which changes are significant from a product perspective — it can only categorize and summarize what the commits describe. The value is in eliminating the drafting toil, not removing the judgment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Case 5: Incident Runbook Generation
&lt;/h2&gt;

&lt;p&gt;What it does: During an incident, an engineer provides the service name, symptoms, and environment. The LLM generates a structured runbook with immediate actions, investigation steps, escalation criteria, and rollback procedures.&lt;/p&gt;

&lt;p&gt;Why during the incident, not after: Post-incident runbooks are valuable for future incidents. An LLM-generated runbook during the incident is useful for the current one — particularly for incidents involving services the on-call engineer did not build or has not operated recently.&lt;/p&gt;

&lt;p&gt;The output is not authoritative — it is a starting point that reduces the cognitive load of structuring an investigation when pressure is highest. An experienced engineer reviewing the generated runbook will immediately identify what is relevant and what is not. That judgment still requires a human.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Does Not Work — The Honest Assessment
&lt;/h2&gt;

&lt;p&gt;Business context is invisible to the LLM. Every LLM integration in CI/CD operates on artifacts — diffs, logs, plans — without understanding why the code was written this way, what the product requires, or what technical debt exists in adjacent systems. Use cases that require this context will produce low-quality output regardless of prompt quality.&lt;/p&gt;

&lt;p&gt;Alert fatigue is a real failure mode. An AI review that produces too many findings with insufficient signal-to-noise ratio will be ignored. Ignored automated output is worse than no automated output — it conditions engineers to dismiss signals from that source, including future signals that matter. Prompt engineering to maximize precision over recall is essential.&lt;/p&gt;

&lt;p&gt;Cost compounds at scale. At 100+ MRs per month with multiple files per MR, the token economics of model selection are significant. Benchmarking the most cost-efficient model that produces acceptable output quality for each use case is engineering work worth doing before production deployment.&lt;/p&gt;

&lt;p&gt;Security requires careful architecture. Code diffs, infrastructure plans, and pipeline logs can contain sensitive information — partially redacted secrets, internal hostnames, service account names. Sending these to external APIs requires explicit data classification and policy decisions. For organizations with strict data residency requirements, self-hosted model deployment may be necessary.&lt;/p&gt;

&lt;p&gt;Prompt management is ongoing work. Prompts that work well for a Python/Django codebase need adjustment for a Go microservices architecture. Prompts that produce good results for a startup with 10 engineers need tuning for a team of 100. Treating prompts as static configuration written once is a mistake — they require the same iteration discipline as any other system component.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture Patterns That Work
&lt;/h2&gt;

&lt;p&gt;Where to integrate in the pipeline: Pre-merge is appropriate for code review and infrastructure plan review — the value is in catching issues before they land. Post-merge is appropriate for release notes generation — it needs the complete merge context. Triggered on failure is appropriate for pipeline diagnosis — it needs the actual failure output.&lt;/p&gt;

&lt;p&gt;Failure handling: Every LLM integration point should be non-blocking by default. allow_failure: true in GitLab CI, post { failure } in Jenkins. External API availability should never gate a deployment.&lt;/p&gt;

&lt;p&gt;Prompt management as code: Store prompts in version-controlled files alongside the pipeline code that uses them. Changes to prompts should go through the same review process as changes to pipeline logic. This enables rollback when prompt changes degrade output quality and provides an audit trail.&lt;/p&gt;

&lt;p&gt;Model selection by use case:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;Recommended Model Type&lt;/th&gt;
&lt;th&gt;Reasoning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Code review (high volume)&lt;/td&gt;
&lt;td&gt;High-throughput optimized&lt;/td&gt;
&lt;td&gt;Volume makes cost significant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failure diagnosis&lt;/td&gt;
&lt;td&gt;Reasoning-optimized&lt;/td&gt;
&lt;td&gt;Accuracy matters more than cost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terraform security review&lt;/td&gt;
&lt;td&gt;Reasoning-optimized&lt;/td&gt;
&lt;td&gt;Security context requires deeper reasoning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Release notes&lt;/td&gt;
&lt;td&gt;High-throughput optimized&lt;/td&gt;
&lt;td&gt;Low complexity, high frequency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runbook generation&lt;/td&gt;
&lt;td&gt;Reasoning-optimized&lt;/td&gt;
&lt;td&gt;Quality matters most under pressure&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Where This Is Heading
&lt;/h2&gt;

&lt;p&gt;The use cases described above share a common characteristic: the LLM produces text that a human reviews and acts on. The next evolution — already underway — moves toward LLMs that take action directly: identifying a failing test, generating a fix, opening a PR, and requesting human approval before merge.&lt;/p&gt;

&lt;p&gt;This agentic pattern changes the risk profile significantly. An LLM that writes a comment a human ignores has limited blast radius. An LLM that opens PRs and modifies infrastructure requires careful guardrails, approval workflows, and explicit scope limitation.&lt;/p&gt;

&lt;p&gt;The engineering work of building those guardrails — defining what the agent can and cannot do, implementing approval gates, auditing actions — is substantial. Teams that have done the work of integrating LLMs into CI/CD as informational tools will have the foundation and the operational experience to take that next step safely. Teams that have not will be starting from scratch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;CI/CD pipelines have always had the data that LLMs need — code diffs, build logs, infrastructure plans, deployment events. What has changed is that LLMs can now do something useful with that data at production scale and at a cost that justifies the integration.&lt;/p&gt;

&lt;p&gt;The use cases that work are specific: pattern recognition in code review, natural language generation for diagnosis and documentation, cross-cutting security analysis that does not fit rule-based tools. The use cases that do not work are equally specific: anything requiring business context, anything where false positive rate creates alert fatigue, anything where the blast radius of incorrect output is unacceptable without human review.&lt;/p&gt;

&lt;p&gt;The engineering teams that will get the most value from LLM integration in CI/CD are the ones that start with a specific problem, instrument it carefully, measure the signal-to-noise ratio honestly, and expand from there.&lt;/p&gt;

&lt;p&gt;The question is not whether LLMs belong in your CI/CD pipeline. For most engineering organizations, they already do. The question is whether you are being deliberate about which problems they are solving and honest about where they fall short.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devops</category>
      <category>infrastructure</category>
      <category>software</category>
    </item>
    <item>
      <title>I Added Claude to Our MR Pipelines. It Now Reviews Every Code Change Before Humans Do.</title>
      <dc:creator>Manikanta Suru</dc:creator>
      <pubDate>Tue, 12 May 2026 17:32:42 +0000</pubDate>
      <link>https://dev.to/manikanta_suru_92/i-added-claude-to-our-mr-pipelines-it-now-reviews-every-code-change-before-humans-do-2epe</link>
      <guid>https://dev.to/manikanta_suru_92/i-added-claude-to-our-mr-pipelines-it-now-reviews-every-code-change-before-humans-do-2epe</guid>
      <description>&lt;p&gt;Three incidents. Three things that passed human code review and shouldn't have.&lt;/p&gt;

&lt;p&gt;A developer pushed an API key into a test repository. It sat committed in the git history for days before anyone noticed.&lt;/p&gt;

&lt;p&gt;A Terraform merge request got approved with a security group ingress rule open to 0.0.0.0/0. It went to pre production.&lt;/p&gt;

&lt;p&gt;A GitLab API token ended up hardcoded in a README.md file. The README. The file everyone reads. Merged without a flag.&lt;/p&gt;

&lt;p&gt;I'm the sole DevOps engineer at a pre-seed energy startup. I support a 10+ person engineering team across backend, frontend, and Terraform infrastructure. We process 100+ merge requests every month. I can't be in every MR. And even when senior engineers reviewed, things slipped through — especially at the end of the week, especially under deadline pressure, especially on Terraform where most developers aren't fluent.&lt;/p&gt;

&lt;p&gt;So I added a Claude AI review stage to our Jenkins MR validation pipeline. Now every merge request gets an automated AI review before any human sees it — posting comments directly on the changed code in GitLab, flagging security issues, checking standards, catching the things tired humans miss.&lt;/p&gt;

&lt;p&gt;Here's exactly how it works.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture: Jenkins + GitLab + Claude
&lt;/h2&gt;

&lt;p&gt;Our setup uses GitLab for source control and Jenkins for CI/CD. When a developer opens or updates a merge request in GitLab, a webhook fires and triggers our Jenkins pipeline. Inside that pipeline lives a file called &lt;code&gt;Jenkinsfile-mr-validation&lt;/code&gt; — our dedicated MR validation pipeline. One of its stages runs the Claude AI review.&lt;/p&gt;

&lt;p&gt;The flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Developer opens/updates MR in GitLab
          │
          ▼
GitLab webhook fires → Jenkins triggered
          │
          ▼
Jenkinsfile-mr-validation runs
          │
    ┌─────┴──────────────────────┐
    │  Stage 1: Lint &amp;amp; Validate  │
    │  Stage 2: Unit Tests       │
    │  Stage 3: Claude AI Review │ ← this is what we're building
    │  Stage 4: Security Scan    │
    └────────────────────────────┘
          │
          ▼
Claude fetches MR diff from GitLab API
          │
          ▼
Claude reviews changed files
          │
          ▼
Comments posted back to GitLab MR inline
          │
          ▼
Developer sees AI feedback before human reviewer arrives
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI review stage never blocks a merge. It's informational — a first-pass that runs automatically so human reviewers can focus on what actually needs human judgment.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Jenkinsfile Stage
&lt;/h2&gt;

&lt;p&gt;Inside &lt;code&gt;Jenkinsfile-mr-validation&lt;/code&gt;, the Claude review stage looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;pipeline&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;

    &lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ANTHROPIC_API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'anthropic-api-key'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;GITLAB_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'gitlab-api-token'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;stages&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Lint &amp;amp; Validate'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// ... existing stages&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Claude AI Review'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;expression&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;gitlabActionType&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'MERGE'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; 
                             &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;gitlabActionType&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'UPDATE'&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;script&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'''
                        pip install anthropic python-gitlab --quiet
                        python3 scripts/claude_mr_review.py \
                            --project-id ${gitlabMergeRequestTargetProjectId} \
                            --mr-iid ${gitlabMergeRequestIid}
                    '''&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;failure&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'Claude review failed — continuing pipeline'&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Security Scan'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// ... existing stages&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two important details:&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;when&lt;/code&gt; condition ensures this stage only runs on actual MR events — not on branch pushes or scheduled builds. No point reviewing code that isn't being merged.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;post { failure }&lt;/code&gt; block means the pipeline continues even if the AI review script crashes or the Anthropic API times out. A failed AI review should never block a deployment.&lt;/p&gt;

&lt;p&gt;Credentials are stored in Jenkins credential store — &lt;code&gt;anthropic-api-key&lt;/code&gt; and &lt;code&gt;gitlab-api-token&lt;/code&gt; — never hardcoded. Ironically, hardcoded credentials were one of the problems this system was built to catch.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Python Review Script
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;gitlab&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_mr_changes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gl_client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;project_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mr_iid&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Fetch changed files from GitLab MR.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;project&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gl_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;mr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mergerequests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mr_iid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;changes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;changes&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;mr&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;detect_file_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Route to correct review prompt based on file type.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.tf&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.tfvars&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;terraform&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.js&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.ts&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.jsx&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.tsx&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.vue&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;frontend&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.py&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.java&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.go&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.rb&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;backend&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;general&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_system_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_type&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Build file-type-aware review prompt.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;You are a senior software engineer doing a code review.
Analyze the diff and identify real issues only.

Format every finding as:
SEVERITY: [CRITICAL/HIGH/MEDIUM/LOW]
FILE: [filename]
ISSUE: [specific description]
SUGGESTION: [concrete fix]

Rules:
- Maximum 10 findings per review
- Only report genuine issues — no hypotheticals
- CRITICAL = security risk or data loss potential
- HIGH = bug or significant quality issue
- MEDIUM = code quality concern
- LOW = style or minor improvement
- If nothing significant found, say so clearly&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;terraform_checks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;

Terraform-specific checks:
- Security groups with ingress open to 0.0.0.0/0 on any port
- Unencrypted RDS instances, EBS volumes, or S3 buckets
- IAM policies with wildcard actions or resources
- Resources missing required tags (Name, Environment, Owner)
- Sensitive outputs without sensitive=true
- Hardcoded credentials or tokens in any value&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;frontend_checks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;

Frontend-specific checks:
- User input rendered without sanitization (XSS risk)
- Sensitive data written to localStorage or console
- Hardcoded API endpoints, keys, or tokens
- Missing input validation on form fields&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;backend_checks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;

Backend-specific checks:
- Hardcoded credentials, API keys, or tokens
- Missing authentication or authorization checks
- SQL queries built with string concatenation
- Sensitive data written to logs
- Missing error handling in async operations&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;file_type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;terraform&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;terraform_checks&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;file_type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;frontend&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;frontend_checks&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;file_type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;backend&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;backend_checks&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;review_file_with_claude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file_type&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Send file diff to Claude for review.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="c1"&gt;# Using Haiku — faster and cheaper than Sonnet or Opus
&lt;/span&gt;    &lt;span class="c1"&gt;# At 100+ MRs/month, cost compounds quickly
&lt;/span&gt;    &lt;span class="c1"&gt;# Haiku handles code review quality well for this use case
&lt;/span&gt;    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Truncate large diffs — stay within token limits
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;diff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;[diff truncated — file too large]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-haiku-4-5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;build_system_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_type&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Review this diff for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;post_review_comment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;review_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Post review as MR comment in GitLab.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;comment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;### 🤖 AI Review — `&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;`&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;review_text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;comment&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;*Automated review. Human approval still required.*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;mr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--project-id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--mr-iid&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;gl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gitlab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Gitlab&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;GITLAB_URL&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://gitlab.com&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;private_token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;GITLAB_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_mr_changes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;gl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;project_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mr_iid&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Failed to fetch MR: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Exit 0 — don't fail the pipeline
&lt;/span&gt;
    &lt;span class="n"&gt;reviewed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;change&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;change&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;new_path&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;diff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;change&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;diff&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Skip deleted files and trivial changes
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;change&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;deleted_file&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="n"&gt;file_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;detect_file_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Skip binary and unknown file types
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;file_type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;general&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="n"&gt;review&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;review_file_with_claude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;review&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;no significant&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;review&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="nf"&gt;post_review_comment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;review&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;reviewed&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;reviewed&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;mr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;## 🤖 AI Code Review&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;✅ No significant issues found in changed files.&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*Human review still required before merging.*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AI review complete. Reviewed &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;reviewed&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; files.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What Claude Actually Catches
&lt;/h2&gt;

&lt;p&gt;After running this across 100+ MRs per month, the pattern is clear.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The three incident types that originally motivated this have not recurred since deployment.&lt;/strong&gt; Hardcoded credentials get flagged at CRITICAL severity before any human reviewer sees the MR. Terraform security groups with permissive ingress rules get caught in the diff before they reach production. Tokens in documentation files get flagged immediately.&lt;/p&gt;

&lt;p&gt;Beyond those:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Missing error handling in async backend functions&lt;/li&gt;
&lt;li&gt;Unvalidated user input in frontend code&lt;/li&gt;
&lt;li&gt;Terraform resources missing required tags&lt;/li&gt;
&lt;li&gt;Debug logging that prints sensitive data&lt;/li&gt;
&lt;li&gt;IAM policies broader than necessary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What it misses:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Claude sees the diff — not the system. It doesn't know why this code was written this way, what the business logic requires, what the rest of the codebase looks like, or whether this change conflicts with something in a file that wasn't modified. Business logic bugs, architectural issues, and context-dependent decisions still need human eyes.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Prompt Engineering That Actually Mattered
&lt;/h2&gt;

&lt;p&gt;The first version was nearly useless.&lt;/p&gt;

&lt;p&gt;Claude posted 30+ comments on every MR. A mix of CRITICAL security findings and extremely minor style suggestions — all formatted identically. Developers started dismissing everything. Which is worse than having no AI review at all.&lt;/p&gt;

&lt;p&gt;Three changes fixed it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maximum 10 findings.&lt;/strong&gt; Hard limit in the system prompt. Forces Claude to prioritize. A review with 5 real issues beats one with 25 where 20 are noise. After this change, developers started reading the reviews instead of closing them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Severity classification.&lt;/strong&gt; CRITICAL/HIGH/MEDIUM/LOW on every finding. Developers can triage. CRITICAL gets fixed before merge. LOW is optional. This one change turned AI review from background noise into a signal worth acting on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File-type-specific prompts.&lt;/strong&gt; Terraform security checks are different from Python code quality checks. A generic prompt applied to both produces generic results for both. Separate prompts per file type — with specific checks relevant to that language and context — made findings significantly more relevant.&lt;/p&gt;

&lt;p&gt;The system prompt is where 80% of the value lives. Spend time there.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Still Requires a Human
&lt;/h2&gt;

&lt;p&gt;Claude reviews the diff. It doesn't understand why the code was written this way. It doesn't know what the product decision was. It can't see the technical debt in the rest of the codebase that this change interacts with. It can't tell whether this feature does what the ticket actually asked for.&lt;/p&gt;

&lt;p&gt;Human code review is still required before every merge. Always. Without exception.&lt;/p&gt;

&lt;p&gt;What changed is what human review focuses on. Before this system, reviewers spent attention on mechanical checks — did someone remember error handling, are there obvious security gaps, is this formatted correctly. Now Claude handles that pass. Human reviewers focus on business logic, architecture, and the decisions that actually require knowing the system.&lt;/p&gt;

&lt;p&gt;That's the right division of labor.&lt;/p&gt;




&lt;h2&gt;
  
  
  Results After Running This in Production
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Security incidents from MR review dropped to zero.&lt;/strong&gt; The specific things that used to slip through — committed credentials, open security groups, tokens in documentation — haven't made it through since this system went live.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Junior developers are progressing faster.&lt;/strong&gt; Consistent, detailed, patient feedback on every MR they open. No waiting for a senior engineer's attention. No inconsistency based on who reviews on what day. The AI explains the reasoning behind every suggestion — something a tired senior engineer at the end of a long review queue often skips.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Human reviews are more focused.&lt;/strong&gt; When the mechanical checks are handled automatically, the conversation in human review shifts to what actually needs human judgment. Less back-and-forth on obvious issues. More time on architecture and business logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Setting This Up in Your Environment
&lt;/h2&gt;

&lt;p&gt;What you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitLab with MR webhooks configured to trigger Jenkins&lt;/li&gt;
&lt;li&gt;Jenkins with the GitLab plugin installed&lt;/li&gt;
&lt;li&gt;Anthropic API key stored as Jenkins credential: &lt;code&gt;anthropic-api-key&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;GitLab personal access token (api scope) stored as: &lt;code&gt;gitlab-api-token&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Python 3.9+ available on your Jenkins agent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start with &lt;code&gt;post { failure }&lt;/code&gt; in your Jenkinsfile stage and &lt;code&gt;sys.exit(0)&lt;/code&gt; in your Python script — ensure the AI review never blocks your pipeline when it fails. Build the safety net first. Tune the prompts second.&lt;/p&gt;

&lt;p&gt;The system prompt is your highest-leverage investment. Write specific checks for your codebase, your language stack, and your team's standards. A generic prompt gives you generic results. The specificity is what makes it useful.&lt;/p&gt;




&lt;p&gt;Credentials committed to repositories, security groups left wide open, tokens hardcoded in documentation — these are mechanical failures. They don't require judgment to catch. They require consistent attention that human reviewers, under pressure, sometimes don't have.&lt;/p&gt;

&lt;p&gt;That's what this system does. The judgment calls still belong to the humans.&lt;/p&gt;




&lt;h1&gt;
  
  
  devops #jenkins #gitlab #ai #claude #codereview #cicd #python #anthropic
&lt;/h1&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>devops</category>
      <category>security</category>
    </item>
    <item>
      <title>I Built 20 AI-Powered DevOps Tools Because I Got Tired of Doing This Stuff Manually</title>
      <dc:creator>Manikanta Suru</dc:creator>
      <pubDate>Mon, 11 May 2026 17:47:57 +0000</pubDate>
      <link>https://dev.to/manikanta_suru_92/i-built-20-ai-powered-devops-tools-because-i-got-tired-of-doing-this-stuff-manually-8do</link>
      <guid>https://dev.to/manikanta_suru_92/i-built-20-ai-powered-devops-tools-because-i-got-tired-of-doing-this-stuff-manually-8do</guid>
      <description>&lt;p&gt;I've been a DevOps/SRE engineer for 10+ years.&lt;br&gt;
I've managed 50+ EKS clusters at Apple scale, built OTA firmware&lt;br&gt;
pipelines for 300+ EV chargers, migrated 80 applications to AWS,&lt;br&gt;
and been the sole infrastructure engineer at two energy startups&lt;br&gt;
where I supported teams of 30-40 engineers alone.&lt;br&gt;
In all of that time, certain tasks never stopped being painful.&lt;br&gt;
47 CloudWatch alarms firing at 11pm — and you have to figure out&lt;br&gt;
which 3 actually matter.&lt;br&gt;
A pod CrashLoopBackOff at 2am — logs open, describe output open,&lt;br&gt;
trying to diagnose while half asleep.&lt;br&gt;
A Terraform plan before a production apply — tired, reviewing it&lt;br&gt;
manually, knowing you'll miss something.&lt;br&gt;
A weekly AWS bill spike — someone asks why, you dig through Cost&lt;br&gt;
Explorer for 40 minutes.&lt;br&gt;
I got tired of doing all of this manually. So I built AI agents&lt;br&gt;
for all of it.&lt;/p&gt;

&lt;p&gt;What I Built&lt;br&gt;
devops-ai-toolkit — 20 open source AI-powered tools across&lt;br&gt;
5 sections, built with Python and Groq LLaMA 3.3.&lt;br&gt;
🔗 github.com/manekanttasuru/devops-ai-toolkit&lt;br&gt;
Every tool came from a real problem. None of this is theoretical.&lt;/p&gt;

&lt;p&gt;The Tools — By Section&lt;br&gt;
Kubernetes (4 tools)&lt;/p&gt;

&lt;p&gt;Pod Failure Analyzer — diagnoses CrashLoopBackOff, OOMKilled,&lt;br&gt;
Pending pods automatically from logs + describe output&lt;br&gt;
Cluster Upgrade Advisor — reads your EKS version, scans for&lt;br&gt;
deprecated APIs, produces a prioritized upgrade plan&lt;br&gt;
RBAC Auditor — scans all roles, bindings, service accounts,&lt;br&gt;
flags dangerous permissions ranked CRITICAL/HIGH/MEDIUM/LOW&lt;br&gt;
Network Policy Analyzer — maps pod coverage, finds unprotected&lt;br&gt;
namespaces, generates suggested NetworkPolicy YAML&lt;/p&gt;

&lt;p&gt;AWS (4 tools)&lt;/p&gt;

&lt;p&gt;IAM Analyzer — flags wildcards, missing MFA, old access keys,&lt;br&gt;
over-permissioned roles with risk scoring&lt;br&gt;
Security Group Auditor — finds open ports to 0.0.0.0/0,&lt;br&gt;
orphaned groups, adds remediation commands per finding&lt;br&gt;
VPC Network Analyzer — maps full topology, flags IP exhaustion,&lt;br&gt;
missing flow logs, generates ASCII topology diagram&lt;br&gt;
Unused Resource Hunter — finds idle EC2s, unattached EBS,&lt;br&gt;
unused Elastic IPs, estimates monthly waste in dollars&lt;/p&gt;

&lt;p&gt;Terraform (4 tools)&lt;/p&gt;

&lt;p&gt;Security Plan Reviewer — reads terraform plan output, flags&lt;br&gt;
security issues, rates CRITICAL/HIGH/MEDIUM/LOW with HCL fixes&lt;br&gt;
Drift Detector — runs terraform plan, classifies drift as&lt;br&gt;
INTENTIONAL/ACCIDENTAL/CONCERNING, gives per-resource recommendations&lt;br&gt;
State Analyzer — scans tfstate for orphans, sensitive values,&lt;br&gt;
missing tags, resource age estimation&lt;br&gt;
Compliance Checker — maps your Terraform against CIS/HIPAA/SOC2&lt;br&gt;
with control numbers and compliance score percentage&lt;/p&gt;

&lt;p&gt;Monitoring (4 tools)&lt;/p&gt;

&lt;p&gt;Dashboard Generator — takes a service name and metrics,&lt;br&gt;
generates complete Grafana dashboard JSON ready to import&lt;br&gt;
Log Pattern Analyzer — reads CloudWatch or local logs,&lt;br&gt;
ranks error patterns by frequency and severity&lt;br&gt;
Grafana Alert Router — classifies P1-P4 severity, routes to&lt;br&gt;
right team, posts directly to Slack via webhook&lt;br&gt;
Anomaly Detector — queries Prometheus + CloudWatch, flags&lt;br&gt;
unusual patterns before they cross alert thresholds&lt;/p&gt;

&lt;p&gt;SRE (4 tools)&lt;/p&gt;

&lt;p&gt;Incident Runbook Generator — takes service + symptoms,&lt;br&gt;
produces structured runbook with exact commands&lt;br&gt;
On-Call Handoff Generator — takes current system state,&lt;br&gt;
writes clean handoff brief for incoming engineer&lt;br&gt;
Deployment Risk Scorer — rates LOW/MEDIUM/HIGH/CRITICAL&lt;br&gt;
with go/no-go checklist per change type&lt;br&gt;
Chaos Engineering Planner — generates full experiment plan&lt;br&gt;
with hypothesis, steps, rollback, safety constraints&lt;/p&gt;

&lt;p&gt;Stack&lt;br&gt;
LLM: Groq API — LLaMA 3.3-70b-versatile (fast, free tier available)&lt;br&gt;
Language: Python 3.9+&lt;br&gt;
AWS: boto3&lt;br&gt;
K8s: kubectl via subprocess&lt;br&gt;
No heavy frameworks — each tool is a single Python file&lt;/p&gt;

&lt;p&gt;Quick Start&lt;br&gt;
bashgit clone &lt;a href="https://github.com/manekanttasuru/devops-ai-toolkit" rel="noopener noreferrer"&gt;https://github.com/manekanttasuru/devops-ai-toolkit&lt;/a&gt;&lt;br&gt;
cd devops-ai-toolkit&lt;br&gt;
pip install -r shared/requirements.txt&lt;br&gt;
export GROQ_API_KEY=your_key_here&lt;/p&gt;

&lt;h1&gt;
  
  
  Run any tool — example:
&lt;/h1&gt;

&lt;p&gt;cd kubernetes/pod-failure-analyzer&lt;br&gt;
python main.py&lt;br&gt;
Get a free Groq API key at console.groq.com&lt;/p&gt;

&lt;p&gt;Why Groq + LLaMA&lt;br&gt;
Fast enough for real-time infrastructure tooling. Free tier is&lt;br&gt;
generous for experimentation. LLaMA 3.3 handles technical DevOps&lt;br&gt;
context well. I use Groq in production for my other AI projects&lt;br&gt;
too — MANI AI and BabyMind AI.&lt;/p&gt;

&lt;p&gt;Every tool has a README with example output so you know what&lt;br&gt;
you're getting before you run it.&lt;br&gt;
If you find it useful — a star helps others find it.&lt;br&gt;
If something is broken or you have ideas — open an issue.&lt;br&gt;
🔗 github.com/manekanttasuru/devops-ai-toolkit&lt;/p&gt;

</description>
      <category>aws</category>
      <category>ai</category>
      <category>devops</category>
      <category>sre</category>
    </item>
  </channel>
</rss>
