| title | GitLab integration for DevIntern Code | ||||
|---|---|---|---|---|---|
| sidebarLabel | GitLab Integration | ||||
| description | Implement GitLab issues and experimentally create merge requests on GitLab.com or GitLab Self-Managed. | ||||
| section | Automation | ||||
| order | 6 | ||||
| sidebarHidden | true | ||||
| dateModified | 2026-11-12 | ||||
| tags |
|
@devintern/code can implement work directly from GitLab issues: fetch issue details and comments, run a feasibility check, move status labels, execute your AI agent, commit changes, and post results back on the issue. Both GitLab Cloud and self-hosted instances are supported.
GitLab can also be used independently as the code host. Experimental code-host support creates merge requests on GitLab.com or one configured GitLab Self-Managed instance. Your task may come from any supported tracker; the origin Git remote determines where the pull or merge request is created.
- Bun and
@getdevintern/codeinstalled globally - GitLab personal access token with the
apiscope - Git repository for your project
In .devintern-code/.env:
TASK_TRACKER=gitlab# Cloud default — omit for gitlab.com; set for self-hosted:
GITLAB_BASE_URL=https://gitlab.example.com
GITLAB_TOKEN=glpat_xxxxxxxxxxxx
GITLAB_PROJECT=group/sub/repoGITLAB_BASE_URL— instance root URL. Omit for GitLab Cloud (https://gitlab.comis the default). Self-hosted instances keep their protocol, so internalhttp://hosts work.GITLAB_TOKEN— personal access token from/-/user_settings/personal_access_tokenson the same instance, with theapiscope.GITLAB_PROJECT— project path (group/repo, subgroups allowed:group/sub/repo) or a numeric project ID.
Like GitHub Issues, GitLab has no built-in workflow states that map cleanly across teams, so @devintern/code maps statuses to labels. Create the labels in your project, then configure them in .devintern-code/settings.json using the project path as the key:
{
"gitlab": {
"projects": {
"acme/team/webapp": {
"inProgressStatus": "In Progress",
"todoStatus": "To Do",
"prStatus": "In Review"
}
}
}
}To keep statuses mutually exclusive, also list them in .devintern-code/.env:
GITLAB_STATUS_LABELS=To Do,In Progress,In ReviewWhen a status changes, @devintern/code adds the target label and removes the other labels in this list. Transitioning to closed or done closes the issue instead of applying a label; moving back to an open status reopens it.
Pass an issue number, #number, a group/sub/repo#123 reference, or a full issue URL:
# Issue number
devintern 123 --create-pr
# Full issue URL (self-hosted URLs work too)
devintern https://gitlab.com/acme/team/webapp/-/issues/123 --create-prThis workflow:
- Fetches the issue body, labels, and comments
- Runs a feasibility assessment (skippable with
--skip-clarity-check) - Applies the
inProgressStatuslabel (unless--skip-commentsis set) - Creates a feature branch, runs your agent, commits, and optionally opens a pull or merge request
- Applies the
prStatuslabel after PR creation - Posts implementation or assessment comments on the issue
When the worker creates a merge request it applies the prStatus label (when configured) and records the issue as actioned locally, so is:open label:... no longer re-implements it even though the issue stays open and keeps its trigger label. Re-arm an actioned issue by editing its summary or description, re-opening it, or changing its labels — see Tickets already actioned after a PR.
GitLab code-host support is shipping as an experimental stack: MR creation, manual review addressing, registered-MR polling, base synchronization, CI repair, hosted relay delivery, and repo-local direct webhooks are available. Broad @mention discovery and scheduled GitLab conflict windows are not enabled.
Add a separate code-host profile to .devintern-code/.env:
DEVINTERN_EXPERIMENTAL_GITLAB_CODE_HOST=true
# Omit for GitLab.com. For Self-Managed, use the instance root, including any
# relative installation path.
GITLAB_CODE_HOST_URL=https://gitlab.example.com
# Personal, project, or group access token with API access.
GITLAB_CODE_HOST_TOKEN=glpat_xxxxxxxxxxxx
# Optional: use a separate Maintainer/Owner token only for relay hook setup.
GITLAB_WEBHOOK_ADMIN_TOKEN=glpat_xxxxxxxxxxxxThen run the existing compatible command:
devintern PROJ-123 --create-pr
devintern PROJ-123 --create-pr --pr-target-branch develop--create-pr remains the stable cross-provider flag. On a detected GitLab remote it opens a merge request and reports GitLab-native terminology in the result.
The GitLab task tracker and code-host credentials are deliberately independent. GITLAB_TOKEN is reused only when TASK_TRACKER=gitlab and GITLAB_BASE_URL exactly matches the code-host instance. A token is never forwarded to a different host.
HTTPS, normal SSH URLs, SCP-style SSH URLs, subgroups, custom ports, and relative installation paths are supported. If SSH config uses a hostname alias that differs from the browser URL, list it explicitly:
GITLAB_CODE_HOST_URL=https://gitlab.corp.example/platform
GITLAB_CODE_HOST_ALIASES=corp-git,gitlab-vpnDevIntern recognizes gitlab.com, the configured instance host, and these explicit aliases. It does not probe or guess unknown hosts.
For an internal certificate authority, provide a readable PEM bundle. Do not disable TLS verification:
GITLAB_CODE_HOST_CA_FILE=/etc/company/gitlab-ca.pemBun honors standard HTTP_PROXY and HTTPS_PROXY variables. An integration-specific proxy can override them:
GITLAB_CODE_HOST_PROXY=http://proxy.corp.example:8080- An explicit
--pr-target-branchis authoritative; otherwise DevIntern uses the repository default branch. - Source and target branches are checked through the GitLab API before creation.
- Duplicate recovery requires the same instance, source project and branch, and target project and branch.
PR_LABELSremains supported. Only labels already present in the GitLab project are applied; missing labels produce a warning and are not created.- MR creation failure retains the existing nonfatal PR behavior: the pushed branch remains available and the task run reports the failure.
- Created MRs are stored with provider, instance, numeric project ID, project path, IID, and canonical URL. The dashboard displays the stored URL.
| Capability | GitLab status |
|---|---|
| GitLab.com MR creation | Experimental |
| Latest stable GitLab Self-Managed MR creation | Experimental |
| Personal, project, and group access tokens | Supported for API access |
| Existing labels | Best-effort; missing labels are skipped |
Manual address-review |
Experimental; same-project writable branches only |
| Registered-MR polling | Experimental; DevIntern-created MRs only |
| Conflict/base synchronization | Experimental; auto mode and manual command |
| CI repair | Experimental; registered MRs through polling, relay, or direct webhooks |
| Hosted relay | Experimental; automatic project hooks, no worker inbound port |
| Direct project webhooks | Experimental; repo-local server only |
| Repository-wide mentions | Deferred |
The validated Self-Managed target is the latest stable GitLab release at the time @devintern/code ships. Other REST API v4 versions continue best-effort with a compatibility warning.
With the same experimental flag and code-host credentials used for MR creation, run:
devintern address-review https://gitlab.com/group/project/-/merge_requests/123Self-managed MR URLs below a configured relative installation path are accepted too. DevIntern:
- reads unresolved inline discussions and top-level human notes;
- ignores resolved discussions, system notes, bot users, blocked users, and its own replies;
- refuses fork MRs, identities below Developer access, and source branches the identity cannot push;
- verifies that the local
originis the exact MR project; - revalidates the MR head SHA immediately before pushing; and
- replies with the result without resolving discussions or changing reviewer assignments.
--no-push leaves the local commit unpushed. --no-reply pushes the fix without posting GitLab replies or recording the notes as addressed. GitLab CI repair through --ci-feedback remains deferred.
The workspace worker polls only GitLab MRs that DevIntern successfully created and recorded. It does not scan projects, infer ownership from branch names, or discover repository-wide mentions.
Automatic addressing is triggered by a new unresolved discussion from either an assigned reviewer or a user whose effective project membership is Developer or higher. Permission lookup failures are treated as unknown and fail closed. Approvals are informational; unresolved actionable discussions are the feedback signal.
To narrow eligible actors further, configure a comma-separated username allowlist:
GITLAB_REVIEWER_ALLOWLIST=alice,bobThe poller reconciles merged, closed, deleted, and inaccessible MRs, paginates discussion results, honors API retry instructions, and applies bounded retry backoff when an addressing run fails or is deferred. Explicit adoption of pre-existing MRs is not supported yet.
Registered MRs can use the same guarded base-sync pipeline as GitHub pull requests. In auto conflict-resolution mode, the worker acts only when GitLab definitively reports conflict or need_rebase; checking, blocked, inaccessible, and unknown states never trigger an agent run. The MR must be same-project, the configured identity must have Developer access or higher, and its source branch must be writable.
You can also run synchronization directly:
devintern resolve-conflicts https://gitlab.com/group/project/-/merge_requests/123The resolver fetches the actual target-branch tip, revalidates the MR state and head immediately before publication, and uses a normal fast-forward push—never a force push. Concurrent branch movement is deferred safely. Scheduled GitLab conflict windows remain deferred; a workspace configured with conflict_resolution = "scheduled" records no GitLab synchronization work until provider-neutral scheduling support lands. Manual resolution remains available in every mode.
When [workspace].ci_failure_fix = true, the worker polls pipelines, jobs, and external commit statuses for registered GitLab MRs. It invokes the existing guarded address-review --ci-feedback repair path only for definitive required failures. Failed jobs marked allow_failure and manual, skipped, or canceled work are ignored. Missing, inaccessible, incomplete, or otherwise unrecognized CI data remains unknown—it is never counted as green.
Failure metadata and bounded excerpts from up to five failed job traces are passed to the agent. Before the repair begins, the worker revalidates the MR head SHA and the same-project writable-branch guard. Successful events deduplicate durably; unsuccessful attempts retain the existing CI_FIX_MAX_ATTEMPTS budget and post a GitLab MR note on exhaustion. A new head SHA grants a fresh retry budget.
The normal worker setup discovers every GitLab remote in workspace.toml, creates and tests a project hook, and receives reference-only events without exposing the worker host:
devintern worker init
# or reconnect all GitHub and GitLab code hosts later:
devintern worker connect
# focused GitLab repair/rotation:
devintern worker connect gitlabCreating project hooks requires Maintainer or Owner access. DevIntern first tries GITLAB_WEBHOOK_ADMIN_TOKEN, then the normal code-host token (including the existing same-instance tracker-token fallback). A permission or network failure leaves polling active and continues with the other projects. Multiple projects, nested groups, GitLab.com, custom ports, relative Self-Managed installation paths, explicit SSH aliases, custom CA bundles, and integration-specific proxies use the same per-repository environment layers described above.
GitLab 19+ uses a Standard Webhooks signing token. Older Self-Managed versions automatically fall back to a legacy secret token when the instance rejects the newer field. These webhook credentials are generated for the route and never shown during successful setup or persisted locally. Reconnection updates only the exact remembered DevIntern hook, tests the replacement route end to end, and keeps the previous route briefly during rotation.
Relay deliveries contain only project/change references. The worker matches provider, instance, numeric project ID, project path, MR IID, and branch where supplied, then re-fetches discussions, MR state, mergeability, permissions, and CI state with GITLAB_CODE_HOST_TOKEN. It never trusts webhook payload state for an action. Polling continues as the fallback and correctness sweep.
To remove the exact remembered hooks and relay routes while retaining polling:
devintern worker connect gitlab --disconnectSelf-Managed GitLab must be able to make outbound HTTPS requests to relay.devintern.com. No inbound connectivity to the customer worker is needed. The relay sees the GitLab provider, instance URL, numeric project ID/path, MR IID or branch/head SHA when needed, event type, delivery ID, and timestamp. It never receives GitLab API tokens, source code, diffs, discussion text, CI logs, ticket bodies, or agent prompts/output.
Polling remains the default and fallback. For lower-latency repo-local automation, explicitly configure a GitLab project webhook:
# GitLab 19+ Standard Webhooks signing token (preferred):
export GITLAB_WEBHOOK_SIGNING_TOKEN="whsec_<base64-encoded-32-byte-key>"
# Compatibility secret for older GitLab Self-Managed versions:
export GITLAB_WEBHOOK_SECRET="a-long-random-secret"
devintern webhook serveSet the project webhook URL to https://your-host.example/webhooks/gitlab, configure the matching signing token (GitLab 19+) or legacy secret token, and enable Comments, Merge request events, Pipeline events, and Job events. Creating or editing a project webhook normally requires Maintainer or Owner access; the code-host API token used to process an event still needs the permissions documented above.
The endpoint verifies Standard Webhooks HMAC signatures with timestamp freshness checks or validates the legacy X-Gitlab-Token with a timing-safe exact comparison. If a signature header is present but invalid, DevIntern does not fall back to the legacy token. Deliveries are deduplicated by GitLab's delivery-scoped webhook-id or Idempotency-Key; the configured webhook UUID is never treated as a delivery ID. Only MRs already registered by DevIntern on the configured instance and project are eligible. Notes trigger the existing feedback reconciliation, closed or merged events stop the watch, definitive conflict states trigger guarded base sync, and failed pipeline/job events re-query CI before repair. Advisory jobs and canceled or ambiguous states remain non-actionable. Polling stays enabled, so missed deliveries and unsupported event variants are reconciled later.
Repository-wide GitLab mention discovery remains out of scope.
GitLab review, conflict-resolution, and CI-repair jobs share the workspace supervisor. Checkout preparation waits for admission, and running subprocesses receive cancellation when the worker stops them. Jobs cancelled before admission do not prepare a checkout.
Select multiple issues with familiar qualifiers — @devintern/code translates them to GitLab's list issues filters. Queries are always scoped to GITLAB_PROJECT:
devintern --query "is:open label:bug" --create-pr
devintern --query 'is:open "login flow"' --create-pr
devintern --query "assignee:@me" --create-prSupported qualifiers: is:open / is:closed, label:name (repeatable), assignee:@me / assignee:username, updated:>=<date>. Anything else is free-text search.
The first 100 matching issues are processed in sequence.
GitLab issues have no estimation field, so --estimate runs in comment-only mode: the analysis is posted (or updated) as an issue comment with the suggested points, reasoning, risks, and unclear areas.
| Scope | Needed for |
|---|---|
api |
Full read/write access (recommended) |
read_api |
Read-only setups (fetching issues works; posting comments and label transitions will fail) |
Self-hosted tokens only exist on their own instance — a gitlab.com token cannot authenticate against your on-premises GitLab and vice versa.
- Attachments: files embedded in issue bodies (
/uploads/...links) are downloaded for the agent using your token; other external links stay as references. - Status labels: labels named in
settings.jsonmust already exist in the project. The error message lists available labels when one is missing. - Comments: use
--skip-commentsto skip issue comments and label transitions for a run. - Merge-request automation: creation, manual review addressing, registered-MR polling, automatic conflict resolution, CI repair, hosted relay delivery, and direct webhooks are experimental. Broad mention discovery and scheduled conflict windows remain unsupported.
"Missing required GitLab credentials"
Ensure GITLAB_TOKEN and GITLAB_PROJECT are set in .devintern-code/.env.
"GitLab API error (401)"
Token rejected: check that it was created on the same instance as GITLAB_BASE_URL, has not expired, and carries the api scope.
"Label "In Progress" not found in the project"
Create the label in your project (Issues → Labels) or change the status names in settings.json to match existing labels.
Old status labels pile up on issues
Set GITLAB_STATUS_LABELS to the full list of status label names so transitions remove the previous status.