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:
- A
daemon.reposentry naming this exactowner/repo. Because you wrote this repo's host down, it outranks everything else — including when it resolves togithub.comand the checkout's remote points elsewhere. - A git remote in the checkout belonging to that same owner. An explicit port (
https://ghe.example.com:8443/...) is carried through toGH_HOSTon these interactive paths (see the port caveat in Step 2). - A
daemon.reposentry naming only the owner. This is weaker than the repo's own remote: an entry foracme/widgetssays nothing definite about whereacme/toolslives, and one owner can legitimately straddle github.com and a GHES instance. - A
GH_HOSTalready exported in your environment. - 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:
| Scope | Why |
|---|---|
repo | Read/write access to repositories, issues, and PRs |
read:org | Discover 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) andgru initread the port from the remote and use it when reaching your instance. The autonomous commands (gru do,gru review,gru lab) still invokeghwith 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
| Format | Example | Resolves to |
|---|---|---|
owner/repo | myorg/app | github.com/myorg/app |
name:owner/repo | netflix:myteam/myapp | Uses [github_hosts.netflix] |
host/owner/repo | github.netflix.com/org/svc | Legacy — 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.