Skip to content

feat(cli): link GitLab issues and merge requests from worktree create and set - #22609

Draft
lucacri wants to merge 1 commit into
stablyai:mainfrom
lucacri:lucacri/cli-gitlab-links
Draft

lucacri wants to merge 1 commit into
stablyai:mainfrom
lucacri:lucacri/cli-gitlab-links

Conversation

@lucacri

@lucacri lucacri commented Sep 24, 2026 •

Copy link
Copy Markdown

ELI5

You can link a GitHub issue or a Linear issue to a workspace from the terminal, but not a GitLab one — there was no flag for it. The app could set a GitLab link when you created a workspace from a GitLab issue, and after that nothing could change it from the command line. This adds the two missing flags.

What Changed

orca worktree create and orca worktree set gain --gitlab-issue and --gitlab-mr.

Before: orca worktree set --worktree active --gitlab-mr 77 failed as an unknown flag. A GitLab link could only be set by creating a workspace from a GitLab item in the app.

After:

orca worktree set --worktree active --gitlab-mr !77
orca worktree set --worktree active --gitlab-issue https://gitlab.example.com/group/project/-/issues/42
orca worktree set --worktree active --gitlab-issue null

Each flag accepts a bare number, GitLab's own #42 / !77 prefix, or a full URL on any host including self-hosted and subgrouped paths. null clears the link on set and is refused on create, matching --linear-issue.

Namespace safety. Issues and merge requests are separate namespaces on GitLab, so each flag refuses the other's reference in both spellings — !42 or a /-/merge_requests/42 URL passed to --gitlab-issue is an error rather than a silent link to a different item. This mirrors the rule the GitHub issue/PR parsers already apply.

Absent flags emit nothing. The update spreads raw, so a present-but-undefined key would erase the stored link. Both keys are omitted entirely when their flag is not passed, and there is a test pinning that.

Why

linkedGitLabIssue and linkedGitLabMR have been in the WorktreeSet and create schemas, and in the persisted defaults, for some time. Nothing in the CLI ever sent them, so the capability existed with no way to reach it. Adding the flags is the smallest change that closes that gap — no schema, transport or persistence change.

Why a dedicated parser rather than reusing parseGitLabIssueOrMRNumber. That helper accepts both prefixes and both URL types without reporting which kind it found, which is right for the new-workspace link picker where the user has not said what they are linking. A flag names the slot explicitly, so the value must be checked against it. The new helper delegates URL parsing to parseGitLabIssueOrMRLink and only adds the kind check and a safe-integer guard.

Why each flag writes only its own slot. --linear-issue does not clear a GitHub linkedIssue today, so making the GitLab flags displace other links would be inconsistent with the sibling flag and a behaviour change beyond adding a flag. The one-issue-per-workspace rule belongs to the app dialog; the worktree set help text now says this explicitly rather than leaving a reader to assume the CLI enforces it.

Linked Issue

Visual Proof

N/A — CLI only, no UI surface. Terminal output shown under What Changed.

Testing

pnpm typecheck clean. pnpm run check:code-quality:changed reports 0 findings. The full CLI suite passes: 1354 tests across 130 files, which includes the help-text and command-spec assertions that would catch a flag registered in one surface and missed in another.

New coverage in src/cli/handlers/worktree-gitlab-link.test.ts (23 tests): every accepted spelling, self-hosted and subgroup URLs, trailing URL segments, both cross-namespace rejections, null allowed on set and refused on create, a flag passed with no value, non-GitLab URL shapes, and a 400-digit number that would otherwise reach parseInt as Infinity.

Three end-to-end tests in index-worktree-set.test.ts assert the payload actually sent over the wire, including that both keys are absent when neither flag is passed.

Platforms: macOS. Nothing platform-dependent — flag parsing and a payload field, no paths, shells or child processes. Remote and SSH hosts are unaffected because this only fills in fields the existing worktree.set call already carried.

  • I manually tested these changes locally
  • Automated tests added/updated, or explained why not below

AI Disclosure

Written with Claude (Opus 5) under review.

Review

Old remote hosts drop these writes silently, and this path has no gate. TriStateLinkedIssue degrades anything it does not recognise to undefined, which the set handler then treats as "no update" — so a worktree set --gitlab-mr 77 against a runtime predating these schema fields reports success and changes nothing. That is the existing behaviour of this path for every field on it; there is no assertRuntimeEnvironmentCapability anywhere under src/main or src/cli.

Flagging it because a reviewer may notice the asymmetry: the renderer's metadata-persist path does gate GitLab writes on worktree.linked-work-item-context.v1. Matching that here would mean introducing capability assertion to the CLI/main path, which no field on it does today, so it is deliberately left out of this PR rather than overlooked.

Also worth a look: because the schema silently no-ops a non-numeric value instead of erroring, all value validation has to happen in the CLI. That is why this PR adds a parser rather than passing the raw string through.

Agent skill upstream boundary

  • Not applicable.

Notes

No schema, wire or persistence change — the fields and their TriStateLinkedIssue contract already existed on both the create and set paths.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered
  • pnpm typecheck, pnpm test and the changed-code quality gate pass locally

@lucacri
lucacri force-pushed the lucacri/cli-gitlab-links branch 15 times, most recently from ddd2453 to 455fa1f Compare September 30, 2026 15:05
@lucacri
lucacri force-pushed the lucacri/cli-gitlab-links branch from 455fa1f to f8b0ede Compare October 1, 2026 17:54
… and set

The update and create RPCs already accepted linkedGitLabIssue and
linkedGitLabMR, but no CLI flag reached them, so a GitLab link could only
be set by creating a workspace from a GitLab item in the app.

Add --gitlab-issue and --gitlab-mr to `orca worktree create` and
`orca worktree set`, mirroring --linear-issue: a bare number, GitLab's
own stablyai#42 or !77 prefix, or a full URL on any host including self-hosted;
null clears on set and is refused on create.

Issues and merge requests are separate namespaces on GitLab, so each flag
refuses the other's reference in both spellings -- !42 or a merge_requests
URL in --gitlab-issue is an error, not a silent link to a different item.
An absent flag emits no key at all, since the update spreads raw and a
present-but-undefined key would erase the stored link.

Each flag writes only its own slot, matching how --linear-issue already
behaves; the one-issue-per-workspace rule is the app dialog's, and the
help text says so rather than implying the CLI enforces it.
@lucacri
lucacri force-pushed the lucacri/cli-gitlab-links branch from f8b0ede to ea18ab5 Compare October 1, 2026 19:37

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant