Skip to content

Add support for self-hosted GitHub Enterprise and GitLab instances#12

Draft
facundofarias wants to merge 3 commits into
mainfrom
self-hosted-instances
Draft

Add support for self-hosted GitHub Enterprise and GitLab instances#12
facundofarias wants to merge 3 commits into
mainfrom
self-hosted-instances

Conversation

@facundofarias

@facundofarias facundofarias commented May 2, 2026

Copy link
Copy Markdown
Contributor

Closes #1.

Summary

Adds opt-in support for GitHub Enterprise Server and GitLab self-managed instances. Bitbucket Data Center is deferred (different API entirely; tracked in #1 for a follow-up if demand surfaces).

Backward compatible: existing accounts have no instanceUrl field and continue to hit the canonical hosts. Users on github.com/gitlab.com see no UI change unless they toggle the new option.

What changed

  • New optional instanceUrl field on PlatformAccount. Absent ⇒ canonical service.
  • GitHub API client (REST + GraphQL) and GitLab API client take an optional instanceUrl parameter, parameterizing the base URL (/api/v3 for GHES, /api/v4 for GitLab).
  • Setup page: new "Self-hosted instance?" toggle (GitHub and GitLab only) revealing a single Instance URL input. Token-link helper points at the user's instance. HTTPS-only URL validation.
  • Runtime host permission request (chrome.permissions.request) before the first API call to a self-hosted host. Manifest declares optional_host_permissions: ["https://*/*"] (Chrome) and optional_permissions (Firefox).
  • Settings → Accounts list shows the instance host as a subtitle (@user · gitlab.example.com) for self-hosted accounts.
  • Plan document at docs/plans/self-hosted-instances.md.

Why no Bitbucket Data Center yet

Bitbucket DC uses a different REST API path (/rest/api/1.0/...), different auth (HTTP access tokens vs. Cloud's email:token Basic), and entirely different data shapes. Implementing it would effectively be a new platform client (~5-10 days). Deferring until there's measurable demand. Toggle is hidden for Bitbucket so users aren't misled.

Status: needs real-instance testing

I implemented this without access to a real GitHub Enterprise Server or GitLab self-managed instance, so the code compiles and unit tests pass but end-to-end behavior against a real self-hosted server has not been verified. Help wanted before merging.

Test plan

  • Typecheck (npm run typecheck) — passes
  • Lint (npm run lint) — passes
  • Unit tests (npx vitest run) — 82/82 passing
  • Production builds (npm run build, npm run build:firefox) — both succeed; manifests emit correct optional_host_permissions / optional_permissions
  • Existing canonical (github.com, gitlab.com) accounts keep working — no signature changes that break absent instanceUrl
  • Connect a real GitLab self-managed instance: token validation, repo list, PR list, CI status, discussions, approvals, deployments, merge from popup
  • Connect a real GitHub Enterprise Server instance: token validation, repo list (incl. orgs), PR list, CI/check-runs, GraphQL review threads, deployments, merge from popup
  • Confirm Chrome shows the host-permission prompt on first connect, and the connection is blocked when permission is denied
  • Confirm token rotation / re-auth replaces the stored instanceUrl correctly

Notes for review

  • Pagination (getNextPagePath) now derives the expected origin from the configured base URL, so GHES Link headers pointing at ghes.example.com/api/v3/... are followed correctly. Existing test still covers the canonical path.
  • The optional broad host permission (https://*/*) is only requested when a user explicitly enters a self-hosted URL — users who never toggle self-hosted never see a permission prompt. Worth flagging in the Chrome Web Store re-review notes.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Support connecting to self‑hosted Git instances (GitHub Enterprise Server, GitLab, Bitbucket Data Center).
    • "Self‑hosted instance" toggle and Instance URL input during account setup; instance shown in Settings.
    • Token/help links, repo listing, PR/MR actions and polling use the configured instance URL.
  • Documentation

    • Added a phased plan and verification criteria for self‑hosted instance support.
  • Chores

    • Browser optional host-permission prompt and manifest entries for runtime instance URL permissions.
  • Tests

    • Added tests validating instance-URL normalization and per‑platform routing.

Backward compatible: existing accounts default to canonical hosts
(api.github.com, gitlab.com). The new optional instanceUrl on
PlatformAccount routes API calls to the user's instance instead.

Setup adds a "Self-hosted instance?" toggle (GitHub and GitLab only;
Bitbucket Data Center is deferred). When enabled, the user enters an
HTTPS instance URL; the extension requests runtime host permission
via chrome.permissions.request before validating the token. Settings
shows the host as a subtitle next to @username for self-hosted
accounts.

Phase 1+2 of the plan in docs/plans/self-hosted-instances.md;
Bitbucket DC (different API, ~5-10 days) deferred pending demand.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 2, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 44fe6a3f-c156-4950-908f-5ad3b36103a4

📥 Commits

Reviewing files that changed from the base of the PR and between dc4c51e and 7974f78.

📒 Files selected for processing (6)
  • src/popup/pages/Setup.tsx
  • src/shared/api/github.test.ts
  • src/shared/api/github.ts
  • src/shared/api/gitlab.ts
  • src/shared/instanceUrl.test.ts
  • src/shared/instanceUrl.ts
✅ Files skipped from review due to trivial changes (2)
  • src/shared/instanceUrl.test.ts
  • src/popup/pages/Setup.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/shared/api/github.test.ts
  • src/shared/api/gitlab.ts

Walkthrough

Adds support for self-hosted Git providers by introducing an optional instanceUrl on accounts, URL normalization and permission helpers, refactors GitHub/GitLab API clients to accept per-instance base URLs, threads instance URLs through UI/setup/service-worker flows, updates manifest for optional runtime host permissions, and adds planning/docs and tests.

Changes

Self-Hosted Instance Support

Layer / File(s) Summary
Planning & Docs
docs/plans/self-hosted-instances.md
New phased plan documenting scope, data model change (instanceUrl), UI surface, per-platform API approaches, cross-cutting concerns, tasks, verification, risks, and effort estimates.
Types / Data Shape
src/shared/types.ts
Adds optional instanceUrl?: string to PlatformAccount.
Instance URL Utilities
src/shared/instanceUrl.ts, src/shared/instanceUrl.test.ts
New canonical instance map, normalizeInstanceUrl() (platform-aware), getInstanceUrl(), isSelfHosted(), getDisplayHost(), and permission helpers requestInstanceHostPermission() / hasInstanceHostPermission() with comprehensive tests.
API Client: GitLab
src/shared/api/gitlab.ts, src/shared/api/gitlab.test.ts
Removes fixed BASE_URL, adds resolveBaseUrl(instanceUrl?), updates internal glFetch(baseUrl, ...), threads instanceUrl?: string through exported APIs (getAuthenticatedUser, getUserProjects, mergeMergeRequest, checkIfMerged, fetchMergeRequests) and hydration/helpers (discussions, pipelines, deployments, diff stats); adds routing tests.
API Client: GitHub
src/shared/api/github.ts, src/shared/api/github.test.ts
Adds resolveBaseUrl/resolveGraphQLUrl, refactors ghFetchRaw/ghFetch/ghPaginate to accept baseUrl, updates getNextPagePath(linkHeader, baseUrl) to validate/strip API base path, threads instanceUrl?: string/graphqlUrl through public APIs and PR hydration (CI, reviews, GraphQL, deployments); extends tests for GHES routing and pagination.
Setup UI & Connect Flow
src/popup/pages/Setup.tsx
Adds selfHosted and instanceUrl state, checkbox + Instance URL input, normalizes instance URL, requests runtime host permission before API calls, uses dynamic token/help URLs, cancels on invalid URL or denied permission, passes normalized instance URL into auth calls and stores it on successful connect.
Settings / Account Display
src/popup/pages/Settings.tsx
Computes instanceHost via getDisplayHost() and shows @username · instanceHost subtitle for self-hosted accounts.
Repository Loading
src/popup/pages/Repos.tsx
Passes account.instanceUrl into github.getUserRepos() and gitlab.getUserProjects().
Background / Service Worker Integration
src/background/service-worker.ts
Threads account.instanceUrl into GitHub/GitLab calls for merge, delete-branch, fetch PRs/MRs, check-if-merged, and refresh CI status.
Manifest & Permissions
manifest.json
Adds Chrome optional_host_permissions and Firefox optional_permissions for https://*/* to enable runtime origin permission requests.
Tests
src/shared/*.{test.ts}
Adds/extends tests covering instance URL normalization, GitHub GHES routing and pagination behavior, and GitLab self-managed routing.

Sequence Diagram

sequenceDiagram
    actor User
    participant Setup as Setup Component
    participant Utils as Instance Utilities
    participant Permissions as Runtime Permissions
    participant API as API Client
    participant Storage as Account Storage

    User->>Setup: Enable "Self-hosted" and enter instance URL
    Setup->>Utils: normalizeInstanceUrl(input, platform)
    Utils-->>Setup: normalized URL / null

    alt invalid URL
        Setup-->>User: show validation error
    else valid URL
        Setup->>Permissions: requestInstanceHostPermission(normalized)
        Permissions-->>Setup: granted / denied

        alt denied
            Setup-->>User: show permission error, block connect
        else granted
            User->>Setup: click Connect
            Setup->>API: getAuthenticatedUser(token, instanceUrl)
            API-->>Setup: user info
            Setup->>Storage: save account (with instanceUrl)
            Storage-->>Setup: stored
            Setup-->>User: connection complete
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and concisely describes the main change: adding self-hosted instance support for GitHub Enterprise and GitLab.
Description check ✅ Passed The PR description comprehensively covers what changed, testing results, known limitations, and includes a plan document; however, it lacks explicit verification against the template's checklist format.
Linked Issues check ✅ Passed All primary objectives from issue #1 are met: configurable base URLs added per account, API modules updated for instance URLs, setup validation implemented, GHES and GitLab self-managed supported, Bitbucket deferred.
Out of Scope Changes check ✅ Passed All code changes are directly aligned with issue #1 objectives; no out-of-scope modifications detected beyond stated scope.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch self-hosted-instances

Review rate limit: 2/5 reviews remaining, refill in 24 minutes and 16 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9ba0290ebb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/shared/api/github.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/shared/api/github.ts (1)

617-659: ⚠️ Potential issue | 🟠 Major

Avoid silently masking GraphQL failures with zero defaults

The catch block at lines 657-659 returns zero values for all fields on any GraphQL error, with no fallback mechanism or error indication. This silently masks failures (auth issues, GHES field/version mismatches) and makes it impossible for consumers to distinguish actual zero unresolved comments from a failed fetch, under-reporting review risk.

Since the comment at lines 594-595 confirms REST API cannot reliably expose isResolved or mergeable, consider either:

  • Adding explicit error logging/handling so failures are detectable
  • Implementing a partial fallback (e.g., REST-based comment count approximation for unresolved threads)
  • Returning a nullable or error-state result rather than defaulting to zeros
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/api/github.ts` around lines 617 - 659, The catch currently
swallows GraphQL errors and returns zeroed fields (unresolvedCommentCount,
hasConflicts, additions, deletions) making failures indistinguishable from real
zeros; update the catch to surface the failure by (1) logging the caught error
(include the error object) when ghGraphQL fails, and (2) change the returned
shape to indicate an error state instead of zeros — e.g. return nullable fields
or an explicit error flag/message alongside
unresolvedCommentCount/unresolvedCommentAuthors/hasConflicts (references:
ghGraphQL, GraphQLPullRequestData, unresolvedCommentCount,
unresolvedCommentAuthors, hasConflicts) so callers can detect and handle GraphQL
failures.
🧹 Nitpick comments (1)
src/popup/pages/Setup.tsx (1)

202-210: ⚡ Quick win

Expose expanded/collapsed state on the account panel trigger

The collapsible trigger button should expose aria-expanded (and ideally aria-controls) so assistive tech can track panel state.

As per coding guidelines, "Use ARIA labels, roles (tab, switch, checkbox), aria-live regions, aria-expanded on collapsible sections, and hide decorative icons from screen readers."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/popup/pages/Setup.tsx` around lines 202 - 210, The button trigger for the
account panel does not expose ARIA state; update the button element (the one
with onClick that calls setConnectingPlatform and resetForm) to include
aria-expanded={isExpanded} and an aria-controls attribute referencing the panel
id (e.g. aria-controls={`account-panel-${cfg.platform}`}); also ensure the
collapsible panel element uses the matching id
(id={`account-panel-${cfg.platform}`}) so assistive tech can associate the
trigger with the panel and track expanded/collapsed state.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/popup/pages/Setup.tsx`:
- Around line 119-127: The normalized self-hosted URL can include API path
segments (e.g., /api/v4) which later get duplicated by resolveBaseUrl called
from github.getAuthenticatedUser() or gitlab.getAuthenticatedUser(); update the
Setup flow to strip any trailing API suffixes before using the normalized value:
after calling normalizeInstanceUrl(instanceUrl) (inside the selfHosted branch)
remove trailing segments like /api, /api/v3, /api/v4 (case-insensitive, with or
without trailing slash) and then assign that cleaned value to normalizedInstance
so the subsequent calls to github.getAuthenticatedUser() /
gitlab.getAuthenticatedUser() receive a base host URL without API paths.

In `@src/shared/api/github.ts`:
- Around line 262-263: The delete request is using the raw branch name in the
path which breaks for branch names with slashes; update the URL construction to
URL-encode the branch segment (use encodeURIComponent(branch)) when building
`${baseUrl}/repos/${repoFullName}/git/refs/heads/${branch}` so the fetch call in
this module (the constant `res` creation) uses the encoded branch; keep other
path segments as-is and mirror the approach used in gitlab.ts for path parameter
encoding.

---

Outside diff comments:
In `@src/shared/api/github.ts`:
- Around line 617-659: The catch currently swallows GraphQL errors and returns
zeroed fields (unresolvedCommentCount, hasConflicts, additions, deletions)
making failures indistinguishable from real zeros; update the catch to surface
the failure by (1) logging the caught error (include the error object) when
ghGraphQL fails, and (2) change the returned shape to indicate an error state
instead of zeros — e.g. return nullable fields or an explicit error flag/message
alongside unresolvedCommentCount/unresolvedCommentAuthors/hasConflicts
(references: ghGraphQL, GraphQLPullRequestData, unresolvedCommentCount,
unresolvedCommentAuthors, hasConflicts) so callers can detect and handle GraphQL
failures.

---

Nitpick comments:
In `@src/popup/pages/Setup.tsx`:
- Around line 202-210: The button trigger for the account panel does not expose
ARIA state; update the button element (the one with onClick that calls
setConnectingPlatform and resetForm) to include aria-expanded={isExpanded} and
an aria-controls attribute referencing the panel id (e.g.
aria-controls={`account-panel-${cfg.platform}`}); also ensure the collapsible
panel element uses the matching id (id={`account-panel-${cfg.platform}`}) so
assistive tech can associate the trigger with the panel and track
expanded/collapsed state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 78f50bd9-1380-4320-930a-d616f3d14960

📥 Commits

Reviewing files that changed from the base of the PR and between 83b9da9 and 9ba0290.

📒 Files selected for processing (10)
  • docs/plans/self-hosted-instances.md
  • manifest.json
  • src/background/service-worker.ts
  • src/popup/pages/Repos.tsx
  • src/popup/pages/Settings.tsx
  • src/popup/pages/Setup.tsx
  • src/shared/api/github.ts
  • src/shared/api/gitlab.ts
  • src/shared/instanceUrl.ts
  • src/shared/types.ts

Comment thread src/popup/pages/Setup.tsx
Comment thread src/shared/api/github.ts
facundofarias and others added 2 commits May 2, 2026 11:38
Covers the new instanceUrl helper module (normalization,
canonical-default fallback, self-hosted detection, host display)
and verifies that the GitHub and GitLab API clients route
requests to the configured instance URL while preserving the
canonical default when none is set. Also asserts pagination
links from a GHES /api/v3 base are followed correctly back to
the same GHES host.

31 new tests; full suite is 113 passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two fixes for the self-hosted-instance flow surfaced in PR review:

- normalizeInstanceUrl now takes an optional platform argument. When
  provided, it strips well-known API path suffixes (/api/v3,
  /api/graphql for GitHub; /api/v4 for GitLab) so users who accidentally
  paste an API endpoint don't end up with a double-appended path
  (e.g., /api/v4/api/v4). It also returns null when the cleaned URL
  matches the canonical service URL — Setup uses that to surface a
  clearer "untoggle Self-hosted to use the public service" error
  instead of persisting bogus state.

- resolveBaseUrl in github.ts is now defensive: if a caller passes
  the canonical github.com instance URL (legacy data or any path that
  bypassed Setup), it routes to the cloud REST/GraphQL endpoints
  rather than the broken https://github.com/api/v3.

Adds 9 tests covering the new normalization paths and the defensive
canonical-routing in the GitHub client. Full suite: 122 passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@facundofarias facundofarias marked this pull request as draft May 11, 2026 05:56
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.

Support self-hosted instances (GitHub Enterprise, GitLab self-managed, Bitbucket Server)

1 participant