feat(gitlab-issue-to-mr): add a label-triggered issue-to-MR automation - #533
feat(gitlab-issue-to-mr): add a label-triggered issue-to-MR automation#533VascoSch92 wants to merge 2 commits into
Conversation
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
… 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.
4dfcb02 to
bd6af7e
Compare
|
🤖 OpenHands is reviewing this PR. Head commit: This comment was posted by an AI agent (OpenHands). |
all-hands-bot
left a comment
There was a problem hiding this comment.
This review was created by an AI agent (OpenHands) on behalf of the repository maintainers.
Summary
This PR ports the proven github-issue-to-pr automation to GitLab, adding a new gitlab-issue-to-mr skill, catalog manifest, and 75 unit tests. The code is well-structured, closely follows the established patterns from the GitHub original, and correctly adapts for GitLab's API differences (resource label events, project-scoped IIDs, URL-encoded paths, oauth2 basic auth, Developer role checks, configurable API root).
Taste Rating: Good taste
The structural guarantees from the GitHub original are all preserved: per-project state, label-event dedup, claim-before-clone, credential-free origin, secret allow-list, and a finalization fallback that opens the MR when the agent did not.
Security Consideration: MCP Server Forwarding
The one deliberate divergence from github-issue-to-pr is forwarding the deployment's MCP servers to the spawned conversation. The PR description explicitly calls this out and asks for review feedback.
The concern: github-issue-to-pr deliberately withholds MCP servers ("a connected GitHub MCP server would hand the conversation the same write access the empty secrets payload just withheld"), while this automation forwards them wholesale, matching github-pr-reviewer. The trust boundary difference matters: github-pr-reviewer is triggered by a PR label (requires write access to the repo), while this automation is triggered by an issue label. On GitLab, a Reporter (access level 20, below Developer) can be granted permission to label issues, which is a lower bar than PR write access. An issue-authored prompt can therefore reach everything those MCP servers expose from a lower-trust trigger.
This is a real widening of the attack surface. The prompt does include an untrusted-input boundary instruction, but that is a prompt-level guardrail, not an architectural one. The README and SKILL.md document this trade-off clearly, and reverting is a three-line change. Recommendation: consider whether the MCP forwarding should be opt-in (e.g., a config flag defaulting to off) rather than always-on, given the lower trust boundary of issue-triggered automations. If the maintainers decide the always-on behavior is acceptable given the documentation, the implementation itself is correct and consistent with github-pr-reviewer.
Minor Observation: Agent Push URL vs Script Clone URL
The prompt instructs the agent to push using a URL built from _instance_url() (derived from GITLAB_API_URL), while the script's clone uses _clone_url() which prefers http_url_to_repo from the project data. For self-managed instances where git is served from a different host than the API, the agent's push would target the wrong host and fail. This is not a bug - the script's fallback _push_branch uses origin (set correctly during clone), and the prompt tells the agent to stop if the push fails. But it does mean the agent's self-service path (opening the MR immediately) would not work on split-host self-managed instances, falling back to the script's next-poll finalization. Worth noting in the troubleshooting table if not already.
Verification
- 75 new tests pass (867 total, 14 skipped)
sync_extensions.py --checkpassesnpm run buildproduces no diff- Manifest validates against
catalog.schema.json py_compilepasses
[RISK ASSESSMENT]
- Overall PR: MEDIUM
The code is solid and well-tested, but the MCP forwarding on an issue-triggered automation lowers the trust boundary compared to the GitHub original. The implementation is correct; the risk is in the security posture choice, which the PR author has explicitly flagged for review.
VERDICT: Worth merging, pending maintainer decision on the MCP forwarding security posture.
KEY INSIGHT: The MCP forwarding is the single security decision that separates this automation from its GitHub counterpart, and it deserves an explicit maintainer sign-off given the lower trust boundary of issue-triggered workflows.
Improve this review? If any feedback above seems incorrect or irrelevant to this repository, you can teach the reviewer to do better:
- Add a
.agents/skills/custom-codereview-guide.mdfile to your branch (or edit it if one already exists) with the/codereviewtrigger and the context the reviewer is missing (e.g., "Security concerns about X do not apply here because Y"). See the customization docs for the required frontmatter format.- Re-request a review - the reviewer reads guidelines from the PR branch, so your changes take effect immediately.
- When your PR is merged, the guideline file goes through normal code review by repository maintainers.
Resolve with AI? Install the iterate skill in your agent and run
/iterateto automatically drive this PR through CI, review, and QA until it's merge-ready.Was this review helpful? React with thumbs up or thumbs down to give feedback.
Why
The catalog has a first-class GitHub
issue-to-prautomation, and ticket-to-codeentries 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-prautomation 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 testsuite and self-managed instance support. Whichever lands, the other should close.
Summary
skills/gitlab-issue-to-mr/—SKILL.mdsetup workflow,README.md,references/state-schema.md, andscripts/main.py, the deterministic poller.automations/catalog/gitlab-issue-to-mr/manifest.json— agitlabrepo-picker, a merge-request-mode select, a configurable API root for
self-managed instances, and a bundle pointing at the shipped script.
tests/test_gitlab_issue_to_mr.py— 70 unit tests over what the scriptowns: config validation, project-path normalization, URL encoding, access
level, credential handling, label events, branch naming, MR creation, clone
release, and state.
marketplaces/openhands-extensions.json; regenerateskills/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 MCPservers are forwarded to the spawned conversation, as
github-pr-revieweralready 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_TOKENis still required. Thisdoes widen what an issue-authored prompt can reach to everything those servers
expose.
github-issue-to-prforwards nothing for exactly that reason, so thisis worth an explicit yes or no in review. Reverting it is a three-line change.
What GitLab makes different:
labeledissue eventsresource_label_eventswithaction: "add"iidowner/repoin the pathdraft: trueon the APIDraft:title prefixx-access-tokenbasic authoauth2basic authpermissions.pushapi.github.comIssue Number
Closes #443
How to Test
End to end, on a GitLab project you can push to:
GITLAB_TOKENin OpenHands Settings → Secrets, with theapiscope andat least the Developer role.
/issue-to-mr:setup.openhandslabel to an open issue.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
push is rejected.
https://gitlab.example.com/api/v4.automations/interface.json'sfeaturedAutomationIdsis unchanged; this entryis not featured.
beta and gated behind Duo availability on a top-level group. The API option
stores
GITLAB_TOKENand carries no MCP server. So the MCP path here is abonus 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.