AI-Driven Development with Tekton and Claude Code on OpenShift
In this article, we will learn how to integrate Claude Code with Tekton Pipelines on OpenShift to automatically implement changes requested through GitHub issues. We will use GitHub webhooks to trigger a Tekton pipeline whenever someone adds a comment starting with @tekton. The pipeline will then clone the repository, communicate with the Claude Sonnet model, implement the requested changes, and finally create a pull request with the generated code.
Actually, we can look at this example from a slightly broader perspective. Our goal is to automate the entire development workflow with AI, from receiving a development request to preparing the changes for review. At the same time, we want to run everything in a controlled OpenShift environment, an enterprise Kubernetes platform. It lets us manage container permissions, credentials, resource limits, and workload isolation using standard Kubernetes mechanisms. Of course, we still need to configure these mechanisms properly, especially when running an AI agent that can modify source code.
We have already explored a similar approach in my previous articles about OpenCode on GitHub with Kubernetes and Claude Code on OpenShift with vLLM and Dev Spaces. This time, however, we will take a slightly different approach. Instead of relying on GitHub Actions or running Claude Code inside a development environment, we will use Tekton to orchestrate the entire process directly on OpenShift.
Actually, this is one of the things I like about Tekton. We can use it for much more than building applications or deploying containers. Since every task runs in a container, we can run almost any tool, including AI coding agents. In our case, Claude Code will act as a developer that receives instructions from GitHub, modifies the application, and prepares the changes for review.
Prerequisites
Before we start, we need an OpenShift cluster with Red Hat OpenShift Pipelines installed. This operator provides Tekton Pipelines and Tekton Triggers, which we will use to handle GitHub events and execute our workflow.
We also need a GitHub repository containing the application we want to modify. In this example, I will use a Java application built with Maven. However, the solution doesn’t depend on Java or Maven. Claude Code can work with different programming languages and project structures.
Finally, we need access to the Anthropic API and a GitHub token with permission to push branches and create pull requests. We will store both credentials in an OpenShift Secret rather than putting them directly into the pipeline definition.
Here are the main components of our solution:
- GitHub — hosts the source code, issues, and pull requests.
- Tekton Triggers — receives GitHub webhook events and starts the pipeline.
- Tekton Pipelines — orchestrates the execution of our tasks on OpenShift.
- Claude Code — analyzes the repository and implements the requested changes using Claude Sonnet.
- GitHub CLI — creates a pull request after the pipeline pushes the generated changes.
Build the Claude Code Container Image
Let’s start by preparing a container image for our AI coding agent. First, we need Claude Code CLI to communicate with the Anthropic API and GitHub CLI to create pull requests. We also need Git to clone the repository, commit changes, and push a new branch.
Finally, we will use Red Hat Universal Base Image with Node.js and install the required tools. Claude Code is available through the @anthropic-ai/claude-code npm package. We don’t need an interactive environment because Tekton will execute Claude Code in non-interactive mode. Here is the Dockerfile.
FROM registry.access.redhat.com/ubi9/nodejs-22
USER 0
RUN dnf install -y git jq tar gzip \
&& dnf clean all
RUN npm install -g @anthropic-ai/claude-code
# GitHub CLI z oficjalnego archiwum
ARG GH_VERSION=2.83.2
RUN curl -fsSL \
"https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_amd64.tar.gz" \
-o /tmp/gh.tar.gz \
&& tar -xzf /tmp/gh.tar.gz -C /tmp \
&& cp /tmp/gh_${GH_VERSION}_linux_amd64/bin/gh /usr/local/bin/gh \
&& rm -rf /tmp/gh*
USER 1001DockerfileNow, let’s build the image and push it to Quay.io.
$ podman build -t quay.io/pminkows/claude-code:latest .
$ podman push quay.io/pminkows/claude-code:latestShellSessionThat’s all we need for our custom image. We will reference it later in the Tekton Pipeline.
Configure Tekton Triggers on OpenShift
The next step is to connect GitHub with our OpenShift cluster. We will use Tekton Triggers to receive webhook events and create PipelineRun. We need three resources: TriggerBinding, TriggerTemplate, and EventListener.
First, let’s create a TriggerBinding. It extracts four values from the GitHub webhook payload: repository clone URL, full repository name, issue number, and comment body.
apiVersion: triggers.tekton.dev/v1beta1
kind: TriggerBinding
metadata:
name: issue-comment
namespace: ci
spec:
params:
- name: repo-url
value: $(body.repository.name)
- name: repo-full-name
value: $(body.repository.full_name)
- name: issue
value: $(body.issue.number)
- name: comment
value: $(body.comment.body)YAMLNext, we need a TriggerTemplate. It creates a PipelineRun for our my-pipeline pipeline and passes the values extracted by TriggerBinding.
apiVersion: triggers.tekton.dev/v1beta1
kind: TriggerTemplate
metadata:
name: issue-comment
namespace: ci
spec:
params:
- name: repo-url
- name: repo-full-name
- name: issue
- name: comment
resourcetemplates:
- apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
generateName: issue-comment-
spec:
params:
- name: repo-url
value: $(tt.params.repo-url)
- name: repo-full-name
value: $(tt.params.repo-full-name)
- name: issue
value: $(tt.params.issue)
- name: comment
value: $(tt.params.comment)
pipelineRef:
name: my-pipelineYAMLAfter that, let’s create an EventListener that connects our TriggerBinding and TriggerTemplate. We will also configure a CEL interceptor to filter incoming requests. We only want to accept GitHub issue_comment events with the created action and a comment body starting with @tekton. This means that regular comments won’t start the pipeline. We can use @tekton followed by any instruction we want to send to Claude Code.
apiVersion: triggers.tekton.dev/v1beta1
kind: EventListener
metadata:
name: issue-comment
namespace: ci
spec:
serviceAccountName: pipeline
triggers:
- bindings:
- kind: TriggerBinding
ref: issue-comment
interceptors:
- params:
- name: filter
value: 'header.match(''X-GitHub-Event'', ''issue_comment'') && body.action == ''created'' && body.comment.body.startsWith(''@tekton'')'
ref:
kind: ClusterInterceptor
name: cel
name: issue-comment
template:
ref: issue-commentYAMLOnce we deploy the EventListener, Tekton creates a Kubernetes Service that receives incoming webhook requests.
$ oc get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
el-issue-comment ClusterIP 172.30.181.196 <none> 8080/TCP,9000/TCP 7h34mShellSessionFinally, we have to expose the service outside the cluster as Route using the following command:
oc expose svc el-issue-commentShellSessionFor production environments, we should also validate GitHub webhook signatures and verify the comment author’s permissions. The @tekton prefix is useful for filtering commands, but it doesn’t provide authentication. For now, let’s copy the Route to use it later when configuring the webhook in GitHub.
oc get route el-issue-commentShellSessionCreate the Tekton Pipeline
Now we can define the pipeline that executes our AI agent. Before running it, we need to configure two credentials: ANTHROPIC_API_KEY for Claude Code and GH_TOKEN for GitHub operations. We will store them in an OpenShift Secret named claude-github-secrets.
oc create secret generic claude-github-secrets \
--from-literal=ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
--from-literal=GH_TOKEN="$GH_TOKEN"ShellSessionOur pipeline name is my-pipeline. It accepts the repository URL, full repository name, issue number, comment body, and target branch. The default target branch is master. For simplicity, we will use two tasks. The first Task, issue-info, uses an Alpine Linux container to print the repository URL, issue number, and comment body. It doesn’t do anything complicated – only helps to verify that Tekton correctly receives the information from GitHub.
The second Task, implement-and-create-pr, runs our custom Claude Code image. It clones the repository, creates a new Git branch, and prepares a prompt using the GitHub comment. Since the comment starts with @tekton, we remove that prefix and the whitespace following it before passing the instruction to Claude Code. Then, the task checks whether the comment contains an actual request and executes Claude Code in non-interactive mode using the Sonnet model.
The agent to read and modify files. Once Claude Code finishes, the pipeline checks the modified files, creates a commit, and pushes the new branch. Finally, it uses GitHub CLI to create a pull request against the target branch. Here is the complete pipeline definition.
apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
name: my-pipeline
spec:
params:
- name: repo-url
type: string
- name: repo-full-name
type: string
- name: issue
type: string
- name: comment
type: string
- name: base-branch
type: string
default: main
tasks:
# ==========================================
# TASK 1: Print GitHub Issue information
# ==========================================
- name: issue-info
params:
- name: repo-url
value: $(params.repo-url)
- name: issue
value: $(params.issue)
- name: comment
value: $(params.comment)
taskSpec:
metadata: {}
params:
- name: repo-url
type: string
- name: issue
type: string
- name: comment
type: string
steps:
- name: print
image: alpine:3.20
env:
- name: REPO
value: $(params.repo-url)
- name: ISSUE
value: $(params.issue)
- name: COMMENT
value: $(params.comment)
script: |
#!/bin/sh
echo "===== GitHub Issue Comment ====="
echo "Repository: $REPO"
echo "Issue: $ISSUE"
echo "Comment: $COMMENT"
echo "================================"
# ==========================================
# TASK 2: Claude Code + Create Pull Request
# ==========================================
- name: implement-and-create-pr
runAfter:
- issue-info
params:
- name: repo-url
value: $(params.repo-url)
- name: repo-full-name
value: $(params.repo-full-name)
- name: issue
value: $(params.issue)
- name: comment
value: $(params.comment)
- name: base-branch
value: $(params.base-branch)
taskSpec:
params:
- name: repo-url
type: string
- name: repo-full-name
type: string
- name: issue
type: string
- name: comment
type: string
- name: base-branch
type: string
volumes:
- name: work
emptyDir: {}
steps:
- name: implement
image: image-registry.example.com/ai/claude-code:1.0
workingDir: /workspace
volumeMounts:
- name: work
mountPath: /workspace
computeResources:
requests:
memory: 1Gi
cpu: "500m"
limits:
memory: 4Gi
cpu: "2"
env:
- name: ANTHROPIC_API_KEY
valueFrom:
secretKeyRef:
name: claude-github-secrets
key: ANTHROPIC_API_KEY
- name: GH_TOKEN
valueFrom:
secretKeyRef:
name: claude-github-secrets
key: GH_TOKEN
- name: REPO_URL
value: $(params.repo-url)
- name: REPO_FULL_NAME
value: $(params.repo-full-name)
- name: ISSUE_NUMBER
value: $(params.issue)
- name: COMMENT
value: $(params.comment)
- name: BASE_BRANCH
value: $(params.base-branch)
- name: HOME
value: /workspace/home
script: |
#!/usr/bin/env bash
set -euo pipefail
echo "=== Preparing environment ==="
mkdir -p "$HOME"
git config --global user.name "tekton-ai"
git config --global user.email \
"tekton-ai@users.noreply.github.com"
# GitHub authentication
git config --global credential.helper \
'!f() { echo username=x-access-token; echo password=$GH_TOKEN; }; f'
echo "=== Cloning repository ==="
git clone \
--branch "$BASE_BRANCH" \
-- "$REPO_URL" /workspace/repo
cd /workspace/repo
BRANCH="ai/issue-${ISSUE_NUMBER}-${HOSTNAME}"
git switch -c "$BRANCH"
echo "=== Preparing Claude prompt ==="
printf '%s\n' "$COMMENT" \
> /workspace/issue-request.txt
{
echo "Implement changes in this repository."
echo
echo "GitHub Issue: #${ISSUE_NUMBER}"
echo
echo "User request:"
cat /workspace/issue-request.txt
echo
echo "Instructions:"
echo "- Analyze the existing code."
echo "- Implement the requested changes."
echo "- Add or update tests where appropriate."
echo "- Do not push changes."
echo "- Do not create a pull request."
echo "- Do not modify CI security settings."
echo "- Do not access secrets."
} > /workspace/claude-prompt.txt
echo "=== Running Claude Sonnet ==="
claude -p \
--model sonnet \
--permission-mode acceptEdits \
--allowedTools "Read,Edit,Write,Glob,Grep" \
< /workspace/claude-prompt.txt
echo "=== Checking changes ==="
git status --short
if [ -z "$(git status --porcelain)" ]; then
echo "No changes generated by Claude"
exit 1
fi
echo "=== Committing changes ==="
git add -A
git commit -m \
"feat: implement issue #${ISSUE_NUMBER}"
echo "=== Pushing branch ==="
git push origin "$BRANCH"
echo "=== Creating Pull Request ==="
gh pr create \
--repo "$REPO_FULL_NAME" \
--base "$BASE_BRANCH" \
--head "$BRANCH" \
--title "AI: implement issue #${ISSUE_NUMBER}" \
--body "Closes #${ISSUE_NUMBER}
Changes generated by Claude Code.
Review required before merging."
echo "=== Pipeline completed ==="YAMLFor this demo, we keep Claude Code execution and GitHub operations in a single Task. In a production environment, I would separate these operations to avoid exposing the GitHub write token to the AI agent.
Configure the GitHub Webhook
We have already prepared the container image, Tekton Triggers, and the pipeline. The last configuration step is to connect our GitHub repository to the EventListener. Open the GitHub repository settings and navigate to Webhooks. Click Add webhook and paste the HTTPS address of the OpenShift Route into the Payload URL field. Set the content type to application/json. Instead of selecting all events, choose Let me select individual events and enable only Issue comments.

After saving the webhook, GitHub can send issue comment events to our OpenShift cluster. The EventListener will then apply the CEL filter and create a PipelineRun only for matching comments. We are ready to test the whole workflow.
Running the demo
Now let’s see how everything works in practice. For this demonstration, I will use a Java application built with Maven. The repository contains a pom.xml file with the project version and a README.md file with basic information about the application. Our task is quite simple. We want Claude Code to increase the patch version in pom.xml and update the README with the new version. Of course, we could do this manually in a few seconds. However, the goal here is to demonstrate the entire workflow, from a GitHub issue comment to a pull request generated by an AI coding agent.
Step 1: Create a GitHub Issue
First, let’s create a new issue in our GitHub repository. We can give it a title related to updating the application version. Then, we may add the comment starting with the @tekton prefix as shown below.

That’s all we need to do on GitHub. In Wwebhok’s history you should see recent calls.

Step 2: Trigger the Pipeline on OpenShift
Once we submit the comment, GitHub sends an issue_comment webhook event to our EventListener. The CEL interceptor checks the event type, action, and comment prefix. If everything matches, Tekton creates a new PipelineRun.
Let’s open the OpenShift console and navigate to the Pipelines section. Then, we should see a new run of my-pipeline with two tasks: issue-info and implement-and-create-pr.

The first task prints the repository URL, issue number, and original comment. We can check its logs to confirm that the webhook integration works correctly.

Step 3: Claude Code Implements the Changes
The task with Claude Code analyzes the Maven project, finds the current version in pom.xml, and increases its patch number. It also updates README.md to reflect the new version. Therefore, we don’t need to specify the exact XML element or README section. The agent can inspect the repository and determine which files need changes. Once Claude Code finishes, the pipeline checks the Git working tree. Since the agent has modified the files, it creates a commit and pushes the branch to GitHub.

Step 4: Review the Generated Pull Request
Finally, the pipeline uses GitHub CLI to create a pull request. Let’s go back to GitHub and open the Issues tab. We should see a new PR associated with our original issue.

The important thing is that we didn’t modify either file manually. We only created an issue and added a comment describing the expected changes. Tekton orchestrated the workflow, Claude Sonnet implemented the request, and GitHub received the generated commit and pull request. Of course, we should still review the code before merging it. If everything looks good and the required checks pass, we can merge the pull request into the master branch.

Final Thoughts
In this article, we built an AI-driven development workflow using GitHub, Tekton Triggers, Tekton Pipelines, and Claude Code on OpenShift.
The idea is simple. We use GitHub issue comments to describe development tasks. Tekton receives the webhook and runs Claude Code inside a container. The agent analyzes the repository and implements the requested changes using Claude Sonnet. Finally, the pipeline commits the code and creates a pull request.
What I particularly like about this approach is that we don’t need a dedicated AI agent platform. We can reuse our existing Kubernetes infrastructure and Tekton components to orchestrate AI-powered development tasks.
Our demo only updated a Maven version and README file. However, the same workflow could handle more advanced tasks, including implementing new features, updating dependencies, or refactoring existing code.



Related Posts