copilot-relay
v0.3.3
Published
Yet, just another relay for Claude Code to use a GitHub Copilot subscription.
Maintainers
Readme
copilot-relay
Yet, just another relay for Claude Code to use a GitHub Copilot subscription.
Disclaimer
This is a research-oriented project and is not affiliated with GitHub, GitHub Copilot, Anthropic, or Claude Code. It depends on upstream GitHub Copilot services and undocumented compatibility behavior, so Copilot availability, model access, API behavior, and runtime stability are not guaranteed.
Public API:
POST /v1/messagesPOST /v1/messages/count_tokensGET /v1/modelsGET /healthzGET|HEAD /api/hello
/api/hello is a static reachability probe Claude Code sends on startup and
around real traffic. Like /healthz it never contacts Copilot, so it proves the
relay is listening and nothing more.
Claude WebSearch is bridge-managed: when the model selects the WebSearch tool,
the relay executes Copilot /responses with web_search_preview, then sends the
retrieved context through a final model pass and returns Claude
server_tool_use / web_search_tool_result blocks. The final pass keeps your
other tools available, so the model can act on what it found in the same turn.
Advertising WebSearch no longer costs streaming. The relay reads the model's response only as far as it takes to tell whether a search is coming, so a turn that never searches streams normally — which is most of them, since Claude Code offers the tool on every request.
Unknown API routes return 500 and log method, path, selected headers, and
request payload to help implement compatible endpoints later.
Routing:
| Requested model | Upstream model |
| --- | --- |
| contains opus | claude-opus-5 |
| gpt-5.6-sol[1m], plain gpt-5.6-sol, or another non-Opus alias | gpt-5.6-sol |
The relay advertises gpt-5.6-sol[1m] to Claude Code. The [1m] selector
changes Claude Code's client-side context budgeting only; every Copilot request
still uses gpt-5.6-sol, and the relay cannot enlarge GitHub Copilot's upstream
capacity. An explicit CLI or API model override that bypasses the managed Claude
settings is outside this guarantee.
Claude Code can still show its built-in Haiku and Sonnet picker entries. The
managed model field sets the startup default but does not restrict
availableModels; selecting any non-Opus alias still follows the GPT route.
Install & run
npx copilot-relay@latest auth
npx copilot-relay@latest start
npx copilot-relay@latest restart
npx copilot-relay@latest stopWith claudeSetup: true, start manages ANTHROPIC_BASE_URL, a dummy
ANTHROPIC_AUTH_TOKEN when auth is absent, and the configured GPT default via
the top-level model field in ~/.claude/settings.json. Exact
gpt-5.6-sol model overrides are normalized to the Claude-facing
gpt-5.6-sol[1m] identity; unrelated model choices are preserved.
Config
Config lives at ~/.copilot-relay/config.yaml and is hot-reloaded:
host: 127.0.0.1
port: 4142
copilotBaseUrl: https://api.githubcopilot.com
claudeSetup: true
logLevel: info
logRetentionDays: 3
thinkEffort: max
upstreamTimeoutSeconds: 180
webSearchBackend:copilot-relay writes the resolved config back to this file, so every key is
present after the first start. Shipped defaults therefore apply to fresh
installs only — once a value is in your config.yaml it is never rewritten by
an upgrade. To pick up a changed default, edit the key yourself.
logLevel controls verbosity:
| Level | Logs |
| --- | --- |
| error | Startup, preflight, and request failures |
| info | Errors plus startup status, preflight status, request IDs, upstream lifecycle, and local HTTP status codes |
| debug | Info plus model routing summaries, Copilot upstream timings, and request payloads |
Any other logLevel value is invalid and stops startup.
Valid thinkEffort: none, low, medium, high, xhigh, max.
upstreamTimeoutSeconds controls the maximum time a single Claude request can
spend waiting on upstream Copilot calls, including chat, Responses, preflight,
and bridge-managed WebSearch calls. The default is 180.
webSearchBackend controls bridge-managed Claude WebSearch. Leave it empty to
use gptModel, or set a Copilot Responses model ID such as gpt-5.5.
The same folder stores copilot_token.json for the cached Copilot bearer token, github_token for refresh/login, and logs/ for runtime logs.
CLI
copilot-relay auth
copilot-relay start
copilot-relay restart
copilot-relay status
copilot-relay stopstatus reports whether a relay is running, where it is listening, and whether
it is reachable:
copilot-relay 0.2.5
process running (pid 93744, up 1h 16m)
version 0.2.5
listening http://127.0.0.1:4142
health ok (9ms)
models gpt-5.6-sol[1m], claude-opus-5
upstream not checked (use --deep)
log ~/.copilot-relay/logs/copilot-relay.2026-07-25.log
config ~/.copilot-relay/config.yaml
host 127.0.0.1
port 4142
copilotBaseUrl https://api.githubcopilot.com
claudeSetup true
logLevel info
logRetentionDays 3
thinkEffort max
upstreamTimeoutSeconds 180
webSearchBackend (unset — uses gptModel)
gptModel gpt-5.6-sol
opusModel claude-opus-5
host, port and claudeSetup take effect on restart; the rest hot-reload.The config block is every key readAppConfig() resolved, in
config.default.yaml order. These are the values on disk: eight of them reload
while the relay runs, while host, port and claudeSetup are read once at
startup — the block says so rather than leaving you to discover it after an
edit. --json emits the same values under config, with webSearchBackend as
null when unset so the key set stays the same shape either way.
The first line is the CLI you invoked; the version row is the build the
running daemon reports about itself. They differ after an upgrade that has not
been restarted yet — the new CLI is installed, the old process is still
serving — and status says so rather than letting the header imply the upgrade
took effect:
version 0.2.6 — MISMATCH, 0.3.0 is installed
...
The running relay is 0.2.6; 0.3.0 is installed.
Restart it to serve the installed version: copilot-relay restartA mismatch does not change the exit code: the relay works, it is just not the
build you installed. A daemon older than v0.3.1 does not report a version at
all, which shows as unknown.
--deep additionally sends a real request through Copilot. That is the only
check that proves the relay can actually serve Claude Code — a relay whose
Copilot token expired an hour ago still answers /healthz and /v1/models,
because neither contacts upstream. It is opt-in because it spends a few tokens.
--json emits machine-readable output. Exit codes: 0 running and reachable,
1 not running, 2 running but not usable — the health probe failed, or
--deep was requested and failed. 0 requires a live process and a passing
health probe, so a relay that cannot answer /healthz never reports success.
Logging
At debug, every model request logs the requested model, upstream model, requested think effort, requested thinking, and effective think effort.
Upstream failures are logged at error with full request and response context in the same log file.
Unsupported Claude API requests are logged at error with the local method/path
and detailed request payload.
Logs are written to ~/.copilot-relay/logs/copilot-relay.<local-date>.log. The
active file rotates at local midnight, and files older than logRetentionDays
local calendar days are deleted. Rotation is what makes retention take effect:
without it the single log file's mtime was refreshed by every append, so nothing
ever aged out.
Quick inspection:
tail -f ~/.copilot-relay/logs/copilot-relay.$(date +%F).log
grep -n "Failed to create\\|Startup preflight failed" ~/.copilot-relay/logs/copilot-relay.*.logSee Logs and troubleshooting (中文) for common debugging workflows.
Running it as a service
To keep the relay running across reboots, see wiki/ — per-platform
setup for macOS, Windows, and Linux, in English and 中文, each covering
registration, how to verify it is actually working rather than merely
listening, and how to stop it.
The same pages are published to the wiki tab,
which is generated from wiki/ on every merge. Edit the folder, not the tab.
Development
All documentation lives in wiki/, in English and 中文:
| Page | Covers | | --- | --- | | Architecture (中文) | Modules, request and startup flow, public API, runtime files | | Internals (中文) | Translation, streaming, prompt caching, lifecycle, logging invariants | | Development (中文) | Setup, tests, CI matrix, workflow, releasing | | Configuration (中文) | Every config key, hot reload vs restart | | Logs and troubleshooting (中文) | Log format, grep recipes, failure modes |
npm install
npm run typecheck
npm run build
npm test