Skip to content

feat(gitlab-issue-to-mr): add a label-triggered issue-to-MR automation - #533

Draft
VascoSch92 wants to merge 2 commits into
mainfrom
vasco/gitlab-issue-to-mr-automation
Draft

feat(gitlab-issue-to-mr): add a label-triggered issue-to-MR automation#533
VascoSch92 wants to merge 2 commits into
mainfrom
vasco/gitlab-issue-to-mr-automation

Conversation

@VascoSch92

@VascoSch92 VascoSch92 commented Sep 3, 2026

Copy link
Copy Markdown
Member
  • A human has tested these changes.

Why

The catalog has a first-class GitHub issue-to-pr automation, and ticket-to-code
entries for Jira, Linear, and Bitbucket, but nothing that starts from a GitLab
issue and ends in a GitLab merge request. GitLab-hosted teams could only get
there by keeping their tickets somewhere else. This ports the proven
github-issue-to-pr automation to GitLab.

Note: #525 opened against the same issue on Aug 31. This branch is an independent
port written against current main (post-#531 template rename), with a full test
suite and self-managed instance support. Whichever lands, the other should close.

Summary

  • Add skills/gitlab-issue-to-mr/SKILL.md setup workflow, README.md,
    references/state-schema.md, and scripts/main.py, the deterministic poller.
  • Add automations/catalog/gitlab-issue-to-mr/manifest.json — a gitlab
    repo-picker, a merge-request-mode select, a configurable API root for
    self-managed instances, and a bundle pointing at the shipped script.
  • Add tests/test_gitlab_issue_to_mr.py — 70 unit tests over what the script
    owns: config validation, project-path normalization, URL encoding, access
    level, credential handling, label events, branch naming, MR creation, clone
    release, and state.
  • Register the skill in marketplaces/openhands-extensions.json; regenerate
    skills/index.js, automations/catalog-index.js, automations/bundle-index.js,
    and the README catalog section.

Every structural guarantee of the GitHub original is kept: one poll per project
with its own state document, dedup on the label event id, a claim persisted
before the slow work, a clone that carries no credential, an allow-list of
forwarded secrets, and a finalization pass that opens the merge request itself
when the agent did not.

One deliberate divergence from github-issue-to-pr: the deployment's MCP
servers are forwarded to the spawned conversation, as github-pr-reviewer
already does, so a connected GitLab server gives the agent typed tools instead
of curl. The curl calls stay in the prompt as the fallback, and pushing the
branch is a git operation either way, so GITLAB_TOKEN is still required. This
does widen what an issue-authored prompt can reach to everything those servers
expose. github-issue-to-pr forwards nothing for exactly that reason, so this
is worth an explicit yes or no in review. Reverting it is a three-line change.

What GitLab makes different:

GitHub GitLab
labeled issue events resource_label_events with action: "add"
repo-global issue numbers project-scoped iid
owner/repo in the path URL-encoded project path, so subgroups survive
labels are objects labels are plain strings
draft: true on the API a Draft: title prefix
x-access-token basic auth oauth2 basic auth
permissions.push Developer role, reading the group role when the project states none
fixed api.github.com configurable API root, and the clone URL comes from the project

Issue Number

Closes #443

How to Test

uv run --group test pytest tests/ -q          # 862 passed, 14 skipped
uv run --group test python scripts/sync_extensions.py --check
npm run build                                  # no diff after regeneration
python3 -m py_compile skills/gitlab-issue-to-mr/scripts/main.py

End to end, on a GitLab project you can push to:

  1. Add GITLAB_TOKEN in OpenHands Settings → Secrets, with the api scope and
    at least the Developer role.
  2. Create the automation from the catalog entry, or run /issue-to-mr:setup.
  3. Apply the openhands label to an open issue.
  4. The next poll comments on the issue with the branch and conversation link, and
    a draft merge request follows once the agent stops.

Video/Screenshots

Not applicable — no UI surface. The catalog entry renders through the existing
automation template form.

Notes

  • Leave the configured branch prefix out of any protected-branch rule, or the
    push is rejected.
  • Self-managed instances are configured by their API root, e.g.
    https://gitlab.example.com/api/v4.
  • automations/interface.json's featuredAutomationIds is unchanged; this entry
    is not featured.
  • GitLab's catalog entry offers MCP only on its OAuth connection option, which is
    beta and gated behind Duo availability on a top-level group. The API option
    stores GITLAB_TOKEN and carries no MCP server. So the MCP path here is a
    bonus when a deployment has one connected, never a requirement.

HUMAN: I ran the full test suite and the sync/build checks locally on this branch; the end-to-end GitLab run against a live project has not been done yet.

🤖 Generated with Claude Code

https://claude.ai/code/session_014EUZQRvv9wQ1AGrDNPAGXk

Ports the github-issue-to-pr automation to GitLab, closing the ticket-to-code
gap for the third of the big-three git providers.

The script keeps every structural guarantee of the GitHub original: one poll
per project with its own state document, dedup on the label event id, a claim
persisted before the slow work, a clone that carries no credential, an
allow-list of forwarded secrets, and a finalization pass that opens the merge
request itself when the agent did not.

What GitLab makes different:

- resource label events with action "add" replace GitHub labeled events
- issue IIDs are project-scoped, and project paths are URL-encoded wherever an
  ID is expected, so subgroups survive
- issue labels come back as plain strings
- a draft is a "Draft: " title prefix, not an API flag
- git authenticates the token as the oauth2 user
- the API root is configurable, so self-managed instances work, and the clone
  URL comes from the project rather than being built from parts
- the Developer role check replaces GitHub's push permission check, reading
  the group role when the project states none

Closes #443

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014EUZQRvv9wQ1AGrDNPAGXk
@github-actions github-actions Bot added the type: feat A new feature label Sep 3, 2026
… agent

Matches github-pr-reviewer: the conversation gets agent_settings.mcp_config
whole, so a connected GitLab server gives the agent typed tools rather than the
curl calls the prompt spells out. Those stay as the fallback, and pushing the
branch is a git operation either way, so GITLAB_TOKEN is still required.

An unreadable settings endpoint is a warning, not a dropped task.

This widens what an issue-authored prompt can reach to everything the connected
servers expose. The skill and README now say so, and the setup workflow asks the
operator to confirm it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014EUZQRvv9wQ1AGrDNPAGXk
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: feat A new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add GitLab issue → MR automation (ticket→PR parity with Jira)

1 participant