The app starts in a repository
Codex or another tool opens a checkout and inspects it with commands like git status --short --branch, git remote get-url origin, and git rev-parse --abbrev-ref --symbolic-full-name @{u}.
gh-forgejo-shim is a small compatibility layer for tools that know how to ask GitHub-style questions but are running inside Forgejo repositories. It routes the commands it understands to Forgejo, translates the answers into the shapes those tools expect, and delegates everything else to the real GitHub CLI.
Honest scope: this is not a complete GitHub CLI clone. It supports the gh calls and repository probes that developer tools commonly use for repository, pull request, issue, check, branch, and auth workflows.
A shim is an adapter that sits between a caller and the tool or service the caller thinks it is using. The caller keeps speaking the same language. The shim decides whether it can translate that request, and either handles it or passes it through.
gh pr status --json number,title,url.gh calls, talks to Forgejo when appropriate, and returns enough GitHub-shaped output for the caller to keep working.
The product has two command names and one optional wrapper. The long command, gh-forgejo-shim, is the management command. The shorter gfj alias is for daily use. The optional managed gh wrapper is what lets GitHub-oriented tools find the shim automatically.
Use gfj bootstrap, gfj doctor, gfj auth login, gfj config add-host, and rollback commands to install, verify, and remove shim-owned state.
When the wrapper is first in PATH, a supported gh command in an allowlisted Forgejo repo can be handled by the shim instead of the real GitHub CLI.
Important guardrail: known GitHub hosts such as github.com still delegate to the real gh. A Forgejo host must be explicitly allowlisted before routed commands use Forgejo behavior.
The easiest way to understand the project is to follow a single command from the app to the final output.
A typical routed gh call
gh pr view --json title,url.
~/.local/bin/gh
The wrapper runs gh-forgejo-shim gh "$@" with the original arguments.
gh, or it receives a clear error when the shim cannot honestly support the request.
GUI coding tools usually do not link to this Rust crate. They launch external commands in a working directory. That means setup, PATH, auth, and local Git shape matter as much as the Forgejo API code.
Codex or another tool opens a checkout and inspects it with commands like git status --short --branch, git remote get-url origin, and git rev-parse --abbrev-ref --symbolic-full-name @{u}.
It may call gh --version, gh auth status, gh api user, gh repo view, gh pr status, gh pr list, or gh issue list to decide which UI and workflow features are available.
PATH chooses the wrapperIf the managed gh wrapper appears before the real gh, the call enters this shim. On macOS, GUI apps launched from Finder, Dock, Spotlight, Raycast, or Alfred may need gfj install-gui-path so they inherit the same tool paths your shell sees.
A Forgejo checkout on an allowlisted host routes selected commands through the Forgejo API. A GitHub checkout, unknown command, or non-allowlisted host delegates to the real GitHub CLI.
For JSON calls, the shim filters and names fields the way GitHub CLI callers expect: nameWithOwner, defaultBranchRef, currentBranch, statusCheckRollup, and related fields.
Raw Git is separate: the shim normally intercepts gh, not git. If an app's branch UI depends on raw Git calls, fix the checkout's origin, origin/HEAD, and branch upstream shape too.
The dispatcher is conservative. It only routes a call when the command is in the supported surface and the repository host is an allowlisted Forgejo host.
| Condition | Result | Why |
|---|---|---|
gh pr view in an allowlisted Forgejo repo |
Forgejo route | The command is supported and the repository host is configured as Forgejo. |
gh repo view -R git.example.com/owner/repo |
Forgejo route | -R gives the shim an explicit host, owner, and repository. |
gh auth status --hostname git.example.com |
Forgejo route | Auth probes can route by explicit hostname or by the current repo. |
gh api user with GH_HOST=git.example.com |
Forgejo route | The shim supports the user probe because apps use it to verify auth. |
gh release list |
Delegate | Release commands are not part of the supported Forgejo compatibility surface. |
gh pr view in a GitHub repo |
Delegate | Known GitHub hosts are protected so normal GitHub work stays native. |
| Supported command with unsupported Forgejo-only flag handling | Explicit error | Once routed, unsupported flags fail clearly instead of pretending to work. |
The shim looks for repository identity in this order:
-R or --repo on the command line.GH_REPO, optionally combined with GH_HOST.GH_HOST plus local Git remote owner and repository data.origin.gh repo view
gh pr view
gh pr status
gh pr list
gh pr checks
gh pr create
gh pr comment
gh pr checkout
gh pr diff
gh issue list
gh issue view
gh issue create
gh auth status
gh auth token
gh api user
Normalization is the translation step. Forgejo returns Forgejo-shaped API objects. GitHub-oriented callers ask for GitHub CLI-shaped fields. The shim maps the useful parts, fills safe defaults where GitHub has concepts Forgejo does not expose the same way, and filters the response to the requested --json fields.
| Forgejo source | GitHub-shaped output | What the caller gets |
|---|---|---|
html_url or url |
url |
A web URL field where apps expect to link a PR, issue, or repository. |
created_at, updated_at |
createdAt, updatedAt |
GitHub-style camelCase timestamps. |
user.login, user.username, or user.full_name |
author.login, author.name |
A stable author object for list and view displays. |
| Forgejo commit statuses for a PR head SHA | statusCheckRollup and gh pr checks |
Check names, states, conclusions, descriptions, and links in the buckets apps recognize. |
| Repository owner, name, default branch, topics, features, permissions | nameWithOwner, defaultBranchRef, viewerPermission, and related repo fields |
Enough repository metadata for GitHub-style project detection. |
| No open PR for the current branch | {} for pr view --json or "currentBranch": null for pr status --json |
A successful empty result instead of a broken app state. |
Human-readable commands print compact lines or URLs that resemble GitHub CLI output closely enough for everyday terminal use.
When --json is present, the shim returns selected fields, supports --jq and templates where implemented, and avoids dumping raw Forgejo payloads.
Compatibility detail: created Forgejo PR URLs include a harmless fragment like #codex-pr=/pull/7 so apps that look for GitHub-style /pull/7 markers can still surface the new PR link.
These are the main source files behind the behavior. Start with the dispatcher, then read the handlers and normalization layer.
| File | Responsibility |
|---|---|
| shim.rs | Builds and removes the managed gh wrapper. Refuses to overwrite unrelated files unless --force is used. |
| routing.rs | Loads config, checks allowlisted hosts, detects supported commands, chooses Forgejo routing or real-gh delegation, and records route traces. |
| repo.rs | Parses repository specs from flags, URLs, environment variables, and Git remotes. |
| auth.rs | Finds Forgejo tokens from environment variables, shim storage, macOS Keychain, and common fj, tea, or gitea config files. |
| read_only.rs | Implements the routed gh repo, gh pr, gh issue, gh auth, and gh api user command behavior. |
| forgejo.rs | Talks to Forgejo's /api/v1 endpoints for repositories, PRs, issues, labels, comments, users, diffs, files, and statuses. |
| normalize.rs | Transforms Forgejo objects into GitHub-shaped repository, PR, issue, and check objects. |
| create.rs | Parses PR creation flags, derives default base/head branches, reads body files, and formats created PR URLs for app compatibility. |
| bootstrap.rs | Runs first-time setup: detects the repo, allowlists the host, installs the wrapper, checks PATH, checks auth, and prints repair commands. |
| doctor.rs | Reports setup health for real gh, fj, allowlisted hosts, auth, wrapper order, current repo, and macOS GUI PATH. |
| trace.rs | Writes redacted JSONL trace records with argv, cwd, route decision, host, repo, duration, and exit code when tracing is enabled. |
The project tries to be recoverable. It owns specific files, marks the wrapper it creates, and keeps normal GitHub CLI behavior available.
gfj bootstrap
gfj auth login git.example.com
gfj doctor
bootstrap detects the current repo, allowlists the host when it is not GitHub, installs the wrapper, and prints repair commands for anything it cannot fix.
gfj config add-host git.example.com
gfj install-shim
gfj doctor
Manual setup is useful when you want to control exactly when the host allowlist or wrapper path changes.
FJ_SHIM_TOKEN, FORGEJO_TOKEN, GITEA_TOKEN, then FJ_TOKEN.~/.config/gh-forgejo-shim/auth.json.fj, tea, and gitea config files.gfj uninstall-shim
gfj uninstall-gui-path
gfj auth logout git.example.com
These commands remove shim-owned integration points. They do not uninstall the real GitHub CLI, delete your repository, or remove unrelated files.
When an app says GitHub CLI is unavailable, PR status is missing, or branch state is stale, capture the actual commands before changing compatibility code.
export FJ_SHIM_TRACE="$PWD/codex-probe-trace.jsonl"
export FJ_SHIM_TRACE_BODY=1
gfj trace smoke
gfj trace summarize codex-probe-trace.jsonl
Trace records include the command, working directory, route decision, host, repository, duration, exit code, output sizes, and redacted excerpts when body tracing is enabled. Sensitive token-like values are redacted, and gh auth token output is not exposed in trace excerpts.
Debugging rule: use traces to prove the failing command shape. Do not add broad compatibility just because a tool says "GitHub CLI unavailable." The fix should match the observed argv, cwd, route decision, and output expectation.
Some app checks never call gh. For one launch session, the project can create a temporary git recorder wrapper that forwards to real Git and appends records to the same trace file.
gfj trace git-recorder create "$PWD/codex-probe-trace.jsonl"
gfj trace git-recorder remove /path/to/printed/wrapper-dir