Junie GitLab CI/CD Run AI tasks from GitLab issues or MRs with Junie GitLab CI/CD.
You can roll out Junie GitLab CI/CD for your self-managed GitLab instance. For setup instructions, contact the Junie team at junie@jetbrains.com .
How it works Junie GitLab CI/CD integrates Junie into your GitLab CI/CD pipeline.
When you tag @junie in a comment to a GitLab issue or merge request, the agent is invoked to run the task. Junie runs a CI pipeline to execute the task, creates a new Merge Request with the changes, and posts a comment with a link to the created MR.
Why use Junie GitLab CI/CD? Delegate tasks to Junie. Junie creates merge requests with code changes from issue comments.
Have Junie review MRs. Tag and instruct @junie in a comment to an existing merge request to have the agent review and fix the changes before merging.
Fix issues in projects without opening the IDE. When running the task, the agent is aware of your entire project structure, agent guidelines, and existing code patterns the same way as it would when working directly in the IDE.
Iterate on tasks with follow-up instructions. The MR submitted by Junie needs another iteration? Tag @junie in a comment to it and provide follow-up instructions.
Work on multiple tasks at a time. Junie GitLab CI/CD can work on multiple tasks simultaneously, excluding parallel runs on the same MR branch.
Setup Docker installation Pull the Docker image:
docker pull registry.jetbrains.team/p/matterhorn/public/junie-gitlab:latest
Run the container with the required environment variables:
docker run -d \
-e JUNIE_API_KEY=your_token_here \
-e GITLAB_HOST=gitlab_host \
-p 8080:8080 \
registry.jetbrains.team/p/matterhorn/public/junie-gitlab:latest
Variable
Description
Required
JUNIE_API_KEY
Your authentication token for the Junie. Get token here .
Yes
GITLAB_HOST
Your organization's GitLab host (e.g., https://gitlab.com)
Yes
GITLAB_IGNORE_CERTIFICATE_ERRORS
Set to true to ignore SSL certificate errors when connecting to GitLab. Default is false.
No
GITLAB_PIPELINE_CONFIGURATION_PATH
Path to the GitLab pipeline configuration file. Default is .gitlab-ci.yml.
No
GitLab repository setup Copy and add the .gitlab-ci.yml file to your project in GitLab.
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "api"
variables:
EJ_FOLDER_WORK: "/builds/workspace" # a directory with all files generated by Junie
ENV_FILE: "/builds/workspace/env_vars.env"
GIT_DEPTH: 1
before_script:
- export GIT_REMOTE_URL="${CI_SERVER_PROTOCOL}://oauth2:${APP_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git"
- export DOT_MATTERHORN="$EJ_FOLDER_WORK/.matterhorn"
- |
if [ -n "$ISSUE_ID" ]; then
export JUNIE_BRANCH="junie-issue-$ISSUE_ID-$CI_PIPELINE_ID"
export JUNIE_COMMIT_MSG="[Junie] Automated changes for issue #$ISSUE_ID"
else
export SHORT_REF=$(echo $CI_COMMIT_REF_NAME | cut -c1-8)
export JUNIE_BRANCH="junie-$SHORT_REF"
export JUNIE_COMMIT_MSG="[Junie] Automated changes ($SHORT_REF)"
fi
- apt-get update
- apt-get install -y git jq || true
- mkdir -p $EJ_FOLDER_WORK
- npm install -g @jetbrains/junie-cli
- printenv > $ENV_FILE
after_script:
- |
while IFS='=' read -r key value; do
[[ -z "$key" ]] && continue
if [[ "$key" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; then
if [ -z "${!key+x}" ]; then
export "$key"="$value" || continue
fi
fi
done < "$ENV_FILE"
- rm $ENV_FILE
- export JUNIE_SUMMARY="$(jq -r '.content.output' "$DOT_MATTERHORN"/issue_md/issue.issue)" || true
- export JUNIE_SUMMARY_ESCAPED=$(printf "%s" "$JUNIE_SUMMARY" | jq -R -c . | sed 's/^"\(.*\)"$/\1/') || true
- |
EJ_RUNNER_INFO_DATA=$(echo "$EJ_RUNNER_INFO" \
| jq --arg JUNIE_BRANCH "$JUNIE_BRANCH" --arg JUNIE_SUMMARY_ESCAPED "$JUNIE_SUMMARY_ESCAPED" --arg CI_JOB_STATUS "$CI_JOB_STATUS" --arg CI_PIPELINE_URL "$CI_PIPELINE_URL" '.sourceBranch = $JUNIE_BRANCH | .junieResult = $JUNIE_SUMMARY_ESCAPED | .jobStatus = $CI_JOB_STATUS | .jobUrl = $CI_PIPELINE_URL')
echo "EJ_RUNNER_INFO:"
echo "$EJ_RUNNER_INFO"
echo "EJ_RUNNER_INFO_DATA:"
echo "$EJ_RUNNER_INFO_DATA"
curl -v -L --request POST "${APP_API_URL}/runner/results" \
--header "X-Gitlab-Token: $APP_TOKEN" \
--header "Content-Type: application/json" \
--data "$EJ_RUNNER_INFO_DATA"
- mkdir -p "$CI_PROJECT_DIR/logs"
- cp -r "$DOT_MATTERHORN"/* "$CI_PROJECT_DIR/logs/"
stages:
- build
run_junie:
stage: build
image: node:24-trixie
script:
- echo "===== [STAGE] Setup & Run Junie ====="
- echo "template - $MR_DESCRIPTION"
- junie --cache-dir="$EJ_FOLDER_WORK" --output-format="json" --auth $JUNIE_API_KEY "$EJ_TASK"
- git status
- echo "===== [STAGE] Save Junie Summary ====="
- |
if ! ls $DOT_MATTERHORN/issue_md.* 1> /dev/null 2>&1; then
echo "No $DOT_MATTERHORN/issue_md.* file found!" >&2
ls -la $DOT_MATTERHORN
exit 1
fi
- echo "===== [STAGE] Commit and Push Changes ====="
- |
if git lfs env > /dev/null 2>&1; then
chown -R $(whoami) .git || true
chmod -R u+w .git/hooks || true
git lfs update --force
fi
- git config --global user.name "Junie"
- git config --global user.email "Junie@jetbrains.com"
- git config --global --add safe.directory "$CI_PROJECT_DIR"
- git checkout -b "$JUNIE_BRANCH" || git checkout "$JUNIE_BRANCH"
- git status
- git add -A :!$ENV_FILE ':!*.orig' ':!*.rej' ':!*.class'
- |
if ! git diff --cached --quiet; then
git commit -m "$JUNIE_COMMIT_MSG"
git remote set-url origin "$GIT_REMOTE_URL"
git push --set-upstream origin "$JUNIE_BRANCH"
else
echo "No changes to commit."
fi
artifacts:
name: "junie-logs"
paths:
- logs/**
expire_in: 1 day
when: always
Issue an access token named junie in Project > Settings > Access token or User settings > Access tokens with:
Configure a webhook on the GitLab side. To do so:
Go to Project > Settings > Webhooks > Add new webhook .
Set a URL pointing on your local (using reverse proxy, e.g. ngrok) or remote junie-gitlab instance: https://HOST/api/public/gitlab/webhooks.
Enter your token to the Secret token field.
Enable at least the Comments trigger.
26 January 2026