For operators. This guide is for the person who installs and configures the Oxy server. End users who want to import a repository just click Import from GitHub in the Workspaces UI — they don’t need to read this.
Overview
In multi-workspace mode, Oxy uses a GitHub App (not a personal OAuth app) to:
- Import repositories as workspaces (“Import from GitHub”)
- Clone repositories on the server when a workspace is created
- Read and push commits through the built-in IDE Git panel
The same GitHub App handles both user authentication and repository access. This guide walks through creating the app, configuring permissions, and setting the required environment variables.
GitHub App integration is only needed for multi-workspace mode (the default oxy serve). If you run oxy serve --local, the workspace files already exist on disk and no GitHub integration is required.
Step 1: Create the GitHub App
-
Go to GitHub → Settings → Developer settings → GitHub Apps → New GitHub App
(For an organization app, go to Organization Settings → Developer settings)
-
Fill in the app details:
| Field | Value |
|---|
| GitHub App name | e.g. my-company-oxy |
| Homepage URL | Your Oxy instance URL (e.g. https://app.example.com) |
| Callback URL | https://your-domain.com/github/callback |
| Webhook | Uncheck “Active” — Oxy does not use GitHub webhooks |
| Where can this GitHub App be installed? | ”Any account” (or “Only on this account” for org-only) |
-
Click Create GitHub App.
After creating the app, go to Permissions & Events and set:
Repository permissions
| Permission | Level | Purpose |
|---|
| Contents | Read & Write | Clone repositories, read and push commits |
| Metadata | Read-only | Required by GitHub for all apps |
Organization permissions
| Permission | Level | Purpose |
|---|
| Members | Read-only | Required — allows regular org members (not just admins) to see this installation during the OAuth connect flow |
Without Organization → Members: Read, only GitHub org owners and admins will be able to connect their account. Regular org members will see “GitHub App not installed” even after the app has been installed on the org. Enable this permission and re-install the app if this happens.
Leave all other permissions at “No access” and save.
Step 3: Generate a Private Key
- Scroll to the Private keys section on the app settings page.
- Click Generate a private key.
- GitHub downloads a
.pem file — keep this safe.
The private key content is the value for GITHUB_APP_PRIVATE_KEY. It should start with -----BEGIN RSA PRIVATE KEY-----.
Step 4: Note Your App Credentials
From the app settings page, collect these values:
| Value | Where to find it | Environment variable |
|---|
| App ID | Top of the app settings page | GITHUB_APP_ID |
| App slug | URL: github.com/apps/{slug} | GITHUB_APP_SLUG |
| Client ID | ”OAuth App” section | GITHUB_CLIENT_ID |
| Client secret | Generate one in “OAuth App” section | GITHUB_CLIENT_SECRET |
Step 5: Set Environment Variables
Set the following on your Oxy server:
# GitHub App credentials
GITHUB_APP_ID=123456
GITHUB_APP_SLUG=my-company-oxy
GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA...
...your key content...
-----END RSA PRIVATE KEY-----"
# GitHub OAuth credentials (same App — used for user login)
GITHUB_CLIENT_ID=Iv23liqo62PgXXXXXX
GITHUB_CLIENT_SECRET=abc123...
# HMAC secret for signing OAuth state and selection tokens
# Generate once and store permanently — changing this invalidates all in-flight OAuth flows
GITHUB_STATE_SECRET=$(openssl rand -hex 32)
GITHUB_STATE_SECRET must stay the same across server restarts and deploys. If it changes, users in the middle of an OAuth flow will see “Invalid state parameter” and have to start over. Generate it once, store it in your secrets manager, and never rotate it unless you intentionally want to invalidate active sessions.
The GITHUB_APP_PRIVATE_KEY value must include the full PEM block with literal newlines, not \n escape sequences. In a .env file, wrap the value in double quotes and paste the key with actual line breaks.
# .env
GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA0Z3VS5JJcds3xHn/ygWep4Xh3bzbFa/DJdTpPOBdLQmBFHDm
...rest of key...
-----END RSA PRIVATE KEY-----"
# Encode the .pem file contents as base64
cat your-app.private-key.pem | base64 -w 0
Store the base64 value in your Kubernetes secret and reference it with envFrom or externalSecrets.
Step 6: Install the App
Before users can import repositories, the GitHub App must be installed on the target GitHub organization or user account:
- Go to
https://github.com/apps/{your-app-slug}
- Click Install and select the organization/account
- Choose “All repositories” or specific repositories
- Click Install
After installation, users can connect their account by clicking Sign in with GitHub in the Workspaces UI.
Step 7: Verify Callback URL Routing
Oxy derives the OAuth callback URL automatically from the Origin header of the browser request — no hardcoding is needed. However, ensure:
-
The callback URL in GitHub matches the domain your users access. For example, if users go to
https://app.example.com, the callback must be https://app.example.com/github/callback.
-
Your reverse proxy forwards the
Origin header to the Oxy backend. Most proxies (nginx, Caddy, ALB) do this by default.
-
OXY_API_URL is set if your backend is at a different URL than the frontend:
OXY_API_URL=https://app.example.com/api
How the Connect Flow Works for Users
When a user clicks Import from GitHub in the Workspaces UI, Oxy walks them through connecting their GitHub account and selecting a repository.
Step 1 — Connect a GitHub account
Oxy opens a GitHub OAuth popup. The user authorizes the app, and GitHub redirects back with a short-lived code that Oxy exchanges for an access token. This token is used to discover which GitHub App installations are visible to that user.
There are three possible outcomes:
| Outcome | What the user sees |
|---|
| One installation found | Oxy auto-connects it — the user skips straight to repository selection |
| Multiple installations found | A picker lists the available GitHub accounts/organizations; the user chooses one |
| No installation found | A message tells the user to ask their admin to install the GitHub App first |
Step 2 — Select a repository
Once an installation (GitHub account or organization) is connected, the user sees the repositories that the GitHub App has access to. They pick one and Oxy clones it as a new workspace.
Connecting a different account
If a user wants to import from a different GitHub account or organization, they can click Use a different GitHub account in the connect dialog. This opens a fresh OAuth popup and lets them authorize a different account — the newly authorized account replaces the previous one for future imports.
The GitHub App must be installed on any account or organization before its repositories appear in the picker. See Step 6: Install the App above, or ask the org owner to install it.
Security: How Installation Selection Is Protected
When multiple installations are shown, Oxy issues a short-lived selection token — an HMAC-signed value encoding the eligible installation IDs, bound to the user’s ID and valid for 10 minutes. When the user picks one:
- The client sends
{ installation_id, selection_token } back to the server
- The server verifies the signature, confirms the user ID matches, checks the token is not expired, and validates that the chosen ID is in the token’s list
- Only then is the namespace created
This ensures a user can only connect an installation they were explicitly shown — installation IDs from other tenants cannot be guessed or injected.
Full Environment Variable Reference
# ── Required for GitHub workspace import ─────────────────────────────
GITHUB_APP_ID=123456 # Numeric app ID from app settings page
GITHUB_APP_SLUG=my-company-oxy # App slug (from the app's URL on github.com)
GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----" # Full PEM block from the downloaded .pem file
GITHUB_CLIENT_ID=Iv23liqo62PgXXXXXX # OAuth Client ID from app settings
GITHUB_CLIENT_SECRET=abc123... # OAuth Client Secret from app settings
GITHUB_STATE_SECRET=random-64-char-hex # Random secret for HMAC signing (state + selection tokens)
Troubleshooting
“GitHub App not installed” — but the app IS installed
The most common cause: the Organization → Members: Read permission is not enabled on the GitHub App.
GET /user/installations only returns installations the OAuth user can see. Without Members: Read,
only org owners and admins are included; regular members get an empty list, which Oxy reports as not_installed.
Fix:
- Go to your GitHub App settings → Permissions & Events → Organization permissions
- Set Members to Read-only and save
- GitHub will ask you to re-approve the updated permissions — click Accept
- Try connecting again
“GitHub callback redirects to localhost”
The redirect_uri is derived from the Origin header. Check that your proxy is forwarding the Origin header and that the correct callback URL is registered in the GitHub App settings.
“GitHub App ID not configured in environment”
GITHUB_APP_ID is not set or not being loaded. Verify your .env file is loaded at server startup or that the environment variable is injected by your deployment platform.
“Invalid state parameter”
The HMAC verification on the OAuth state failed. This usually means GITHUB_STATE_SECRET changed between when the user started the OAuth flow and when GitHub redirected back. Ensure the secret is consistent across server restarts (don’t regenerate it on every deploy).
“Invalid or expired selection token”
The user took more than 10 minutes to pick an installation, or the server restarted and GITHUB_STATE_SECRET changed. The user should click Sign in with GitHub again to get a fresh token.
Users see “Sign in with GitHub” but nothing happens
Check that GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET are set correctly. Also verify the callback URL in the GitHub App settings matches your deployment domain exactly.
“Use a different GitHub account” connects the wrong account
The connect popup opens in a new browser window. If the user is already signed into GitHub as a different account in that window, they may need to sign out of GitHub first before authorizing. After a successful authorization, Oxy always uses the most recently connected account.