GitHub Enterprise Server (GHES) Setup

This guide walks you through configuring Gru to work with a GitHub Enterprise Server instance. If you're using github.com, see GETTING_STARTED.md instead.

Prerequisites

Complete the basic Getting Started prerequisites first (Rust, Claude Code). Then come back here for GHES-specific setup.

Step 1: Authenticate gh CLI with your GHES host

Gru delegates all GitHub API calls to the gh CLI, so you need gh authenticated against your GHES instance.

gh auth login --hostname github.netflix.com

Follow the prompts to authenticate. When asked about protocol, HTTPS is recommended for GHES.

Verify it worked:

gh auth status --hostname github.netflix.com

You should see output like:

github.netflix.com
  ✓ Logged in to github.netflix.com account yourname

Multi-host authentication

The gh CLI supports being logged into multiple hosts simultaneously. If you use both github.com and a GHES instance:

# Authenticate to both
gh auth login                                  # github.com
gh auth login --hostname github.netflix.com       # GHES

# Verify both
gh auth status

Gru sets GH_HOST on every gh CLI invocation, so the correct host is always targeted — you don't need to worry about which is the "default".

Interactive sessions (gru chat, gru pm, gru tpm) get GH_HOST too, so gh run inside a session hits the same instance. The host is resolved in this order:

  1. A daemon.repos entry naming this exact owner/repo. Because you wrote this repo's host down, it outranks everything else — including when it resolves to github.com and the checkout's remote points elsewhere.
  2. A git remote in the checkout belonging to that same owner. An explicit port (https://ghe.example.com:8443/...) is carried through to GH_HOST on these interactive paths (see the port caveat in Step 2).
  3. A daemon.repos entry naming only the owner. This is weaker than the repo's own remote: an entry for acme/widgets says nothing definite about where acme/tools lives, and one owner can legitimately straddle github.com and a GHES instance.
  4. A GH_HOST already exported in your environment.
  5. Any recognized remote, when no owner could be determined.

If none of these identify a host, Gru leaves GH_HOST unset and gh applies its own configuration.

Token scope requirements

Your token needs the following scopes:

ScopeWhy
repoRead/write access to repositories, issues, and PRs
read:orgDiscover repos and check org membership

If you authenticate interactively with gh auth login, the default scopes are usually sufficient. If you're using a personal access token, make sure it includes the scopes above.

To verify your token's scopes without exposing the token value, use the GitHub API — the response headers include X-OAuth-Scopes:

gh api --hostname github.netflix.com /user --include 2>&1 | grep -i x-oauth-scopes

Alternatively, navigate to your GHES instance → Settings → Developer settings → Personal access tokens to view scopes in the UI.

Step 2: Configure ~/.gru/config.toml

Create or edit ~/.gru/config.toml and add a named host entry:

[github_hosts.netflix]
host = "github.netflix.com"

The name (netflix in this example) is your shorthand — you'll use it when referencing repos.

host is a bare hostname and must not include a port; Gru rejects the config if it does. A host's identity is portless, and a port written here would never match your repos' remotes. If your instance is served on a non-default port, put it in the git remote URL (https://github.netflix.com:8443/myteam/myapp) — that is where Gru reads it from.

Non-default ports are only partly supported today. Interactive sessions (gru chat, gru pm, gru tpm) and gru init read the port from the remote and use it when reaching your instance. The autonomous commands (gru do, gru review, gru lab) still invoke gh with the portless hostname, so they cannot reach an instance served on a non-default port. Tracked in #938. If your GHES instance is on the standard port (the usual case), nothing here affects you.

Optional: web_url

If the GHES web UI lives on a different domain than the API/git host (uncommon), set web_url. Note that host is a bare hostname while web_url is a full URL including scheme:

[github_hosts.netflix]
host = "github.netflix.com"
web_url = "https://github-web.netflix.com"

Most GHES installations don't need this.

Step 3: Reference repos using the named host

In your config's daemon.repos, reference GHES repos with the name:owner/repo format (this shorthand is for config files only, not CLI arguments):

[daemon]
repos = [
    "netflix:myteam/myapp",
    "netflix:myteam/mylib",
]

This tells Gru that myteam/myapp lives on the host defined in [github_hosts.netflix].

Repo format reference

FormatExampleResolves to
owner/repomyorg/appgithub.com/myorg/app
name:owner/reponetflix:myteam/myappUses [github_hosts.netflix]
host/owner/repogithub.netflix.com/org/svcLegacy — use name:owner/repo instead

The name:owner/repo format is recommended for GHES.

Step 4: Initialize and run

Initialize your GHES repo. If you're passing owner/repo explicitly, use --host:

gru init myteam/myapp --host github.netflix.com

Alternatively, if you run gru init from inside an already-cloned GHES repository, Gru can infer the host from the git remote automatically — no --host flag needed in that case.

Then use Gru normally:

# Work on a single issue (use the full URL for GHES issues)
gru do https://github.netflix.com/myteam/myapp/issues/42

# Or from within the worktree, just use the issue number
gru do 42

# Run lab mode to poll for gru:todo issues
gru lab

Full config example

Here's a complete config for a team using GHES with two repos:

[github_hosts.netflix]
host = "github.netflix.com"

[daemon]
repos = [
    "netflix:myteam/api-gateway",
    "netflix:myteam/web-ui",
]
poll_interval_secs = 60
max_slots = 4

[agent]
default = "claude"

[merge]
confidence_threshold = 9

See config.example.toml for all available options with explanations.

Troubleshooting

gh auth status fails for GHES host

github.netflix.com
  X Not logged in to github.netflix.com

Fix: Run gh auth login --hostname github.netflix.com and authenticate.

"Unknown host name" error

Unknown host name 'netflix' in repo 'netflix:myteam/myapp'.
Add a [github_hosts.netflix] section to config.toml

Fix: Add the missing host entry to ~/.gru/config.toml:

[github_hosts.netflix]
host = "github.netflix.com"

403 Forbidden on API calls

Your token likely lacks required scopes. Re-authenticate with appropriate scopes:

gh auth login --hostname github.netflix.com --scopes repo,read:org

Or generate a new personal access token in your GHES settings with the scopes listed in Step 1.

SSL/TLS certificate errors

If your GHES instance uses a self-signed or internal CA certificate, the most reliable fix is to add the certificate to your system trust store. The gh CLI uses Go's standard TLS stack, which defers to the OS trust store.

On macOS:

sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain /path/to/ca-cert.crt

On Linux (Debian/Ubuntu):

sudo cp /path/to/ca-cert.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates

For git operations only (does not affect gh API calls):

git config --global http.https://github.netflix.com/.sslCAInfo /path/to/ca-bundle.crt

gru init hangs or times out

Check network connectivity to your GHES host:

gh api --hostname github.netflix.com /meta

If this fails, the issue is network-level (VPN, firewall, DNS).

Issue comments or labels not appearing

Verify your token has repo scope and that you have write access to the repository:

gh api --hostname github.netflix.com repos/myteam/myapp

If you get a 404, you either don't have access or the repo path is wrong.