feat(gitlab-issue-to-mr): add a label-triggered issue-to-MR automation - #533
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.
DevinVinson
left a comment
There was a problem hiding this comment.
Looks good to me. I have not pulled down to test but if you need or want another test before merging just let me know.
Thanks. I wanted anyway re-test everything before releasing agent-canvas |
|
@all-hands-bot fix the conflicts |
…alog-index.js Both files are auto-generated. Resolved by accepting main's version (which includes custom-automation and github-issue-triage entries) and regenerating with npm run build and python3 scripts/sync_extensions.py, so the merged result includes the gitlab-issue-to-mr entry from this PR alongside the new entries from main. Co-authored-by: openhands <openhands@all-hands.dev>
|
I've resolved the merge conflicts. Both conflicting files (
The PR is now mergeable — no conflicts remain. All 904 tests pass (24 skipped). Generated by OpenHands AI on behalf of the user. |
|
🤖 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.
Review: feat(gitlab-issue-to-mr): add a label-triggered issue-to-MR automation
Taste Rating: 🟢 Good taste
This is a clean, well-structured port of the proven github-issue-to-pr automation to GitLab. The code is thorough, the security model is sound, and the GitLab-specific adaptations are correct.
Assessment
No material bugs or security issues found.
Repository boundaries: Correct - this is a skill + automation catalog entry + tests, which belongs in the extensions registry. No SDK documentation is added or duplicated.
Structural correctness: Every structural guarantee of the GitHub original is preserved:
- One poll per project with its own state document
- Dedup on the label event id (GitLab
resource_label_eventswithaction: "add") - Claim persisted before the slow work
- Clone carries no credential (token travels via
GIT_CONFIG_VALUE_0env var, never on the command line or in git config) - Allow-list of forwarded secrets (
AGENT_SECRET_NAMES) - Finalization pass that opens the MR when the agent did not
- Clone removal gated on confirmed conversation termination, with path-confined deletion (
is_relative_tocheck)
GitLab adaptations are correct: URL-encoded project paths (subgroups survive), project-scoped IIDs, plain-string labels, Draft: title prefix, oauth2 basic auth for git, configurable API root for self-managed instances, Developer role check reading both project and group access levels.
Tests: 75 tests pass, covering config validation, project path normalization, URL encoding, access levels, credential handling, label events, branch naming, MR creation (including 409 conflict adoption), clone release, state persistence, and prompt construction. Tests exercise real code paths, not just mock-call assertions.
Build/sync: scripts/sync_extensions.py --check and npm run build both pass with no diff.
Design observation (already flagged by the author)
The deliberate divergence from github-issue-to-pr - forwarding the deployment’s MCP servers to the spawned conversation - is explicitly called out in the PR description and asked for review. github-issue-to-pr deliberately withholds MCP servers to avoid widening what an issue-authored prompt can reach. This PR forwards them whole, matching github-pr-reviewer. The security implication is documented in SKILL.md, README.md, and inline comments: everything reachable through those servers is reachable from a prompt written by whoever opened the issue. This is a defensible design choice (the same one github-pr-reviewer makes), and reverting it is a three-line change as stated. The author has asked for an explicit yes/no, so I’ll give one: yes, this is acceptable - the MCP path is a bonus when a deployment has a connected GitLab server, never a requirement, and the curl fallback stays in the prompt.
Minor notes (non-blocking)
- The SKILL.md agent-driven setup path defaults to
*/5 * * * *while the catalog manifest defaults to*/15 * * * *. This is the same discrepancy that exists ingithub-issue-to-pr(inherited pattern), and the two paths are independent configuration surfaces. _instance_url()assumes the API URL ends with/api/v4; a non-standard suffix would produce incorrect clone/push URLs. Theoretical only - GitLab’s API is v4 and the config help text documents the expected format.
Risk Assessment
[RISK ASSESSMENT]
- Overall PR: 🟢 LOW
- Well-tested port of a proven pattern. Security model is sound: no credential in the clone, secrets allow-list, prompt injection defenses in the agent prompt, AI disclosure on all public comments, path-confined clone deletion. The MCP forwarding divergence is a deliberate, documented design choice matching an existing automation.
VERDICT: ✅ Worth merging. Core logic is sound, tests are comprehensive, and the GitLab adaptations are correct.
KEY INSIGHT: A faithful port that adapts every GitLab-specific API difference correctly while preserving the security guarantees of the GitHub original.
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 👍 or 👎 to give feedback.
|
@malhotra5 thanks |
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.