Use your local GitHub Copilot CLI session from any browser (phone, tablet, or second computer) through a self-hosted web relay.
[Browser] <-- WebSocket --> [server.js :3333] <-- HTTP poll --> [Copilot CLI session]
Copilot Remote is built to feel at home on both desktop and mobile. Your conversations can follow you from a browser tab to a PWA install, with file browsing and previews built in.
Mobile session view with the composer and a PWA-style fullscreen layout.
|
Desktop PWA App (Chrome), chat view with session list.
|
Integrated file viewer for previews and inline file browsing.
|
Copilot Remote is split into two pieces:
- Web relay server (
server/): queueing, persistence, auth, browser UI, file browser, uploads. - Copilot CLI extension (
.github/extensions/web-relay/): polls the relay, executes turns, streams activity, bridgesask_userquestions into web question cards.
- Remote chat UI for a local Copilot CLI session
- Per-message mode picker:
plan,ask,agent,autopilot - Per-message model picker (live model discovery + fallback catalog)
- Streaming tool/activity updates while a turn runs
- Web question cards for
ask_userclarification flows - Conversation history stored in local SQLite
- Conversation delete requests are relayed to Copilot CLI SDK
deleteSession()so web deletes can remove resumable CLI sessions - Conversation compact workflow (
/compact) to continue with summary carry-over - Workspace + drives browser with file preview and raw file access
@file:and@folder:reference tokens with copy-to-clipboard helpers- Uploads and image attachment relay support
- Optional SSH reverse tunnel support for internet access
- PWA install support with installed-app fullscreen preference and browser-mode fallbacks
| Requirement | Notes |
|---|---|
| Node.js 18+ | Runs the relay server |
GitHub CLI (gh) |
Must be available in PATH |
| GitHub Copilot CLI extension | gh extension install github/gh-copilot |
| Copilot subscription | Individual, Business, or Enterprise |
git clone https://github.com/materia79/copilot-remote
cd copilot-remote
npm installCreate server/config.json:
{
"authToken": "change-me",
"port": 3333,
"localhostOnly": true,
"pollIntervalMs": 3000,
"processingTimeoutMs": 600000,
"conversationSessionMode": "isolated"
}Start Copilot with the relay extension:
npm run copilot:relayIf you installed the extension globally in ~/.copilot/extensions/web-relay/, you can also start plain Copilot from any repository:
gh copilotIn that setup, the extension auto-starts and supervises server.js for the active CLI session; npm run copilot:relay is just a convenience launcher for this repository.
On Windows, the relay's visible launcher path now targets a stable per-workspace Windows Terminal window name so later foreground launches reuse the same window instead of opening new desktop windows. Use the hidden/stdio fallback only when you explicitly need it.
Open:
http://<your-pc-ip>:3333/
When localhostOnly is true, use http://localhost:3333/ from the same machine.
Sign in once with your token. The relay then uses an HttpOnly auth cookie.
| Command | Purpose |
|---|---|
copilot-remote |
Global npm command (after npm link or npm install -g .) that starts the web relay if needed, then runs gh copilot in the same shell |
npm run copilot:relay |
Starts Copilot CLI with an initial prompt so the extension loads and polling begins |
npm run start:server |
Server only (use with an active Copilot CLI session that loads the extension) |
npm start |
Standalone development mode (server.js + relay.mjs) |
Run only one relay owner at a time:
- Extension-managed mode: Copilot CLI extension handles polling.
- Standalone mode:
npm starthandles polling itself.
Do not run extension polling together with standalone relay polling.
Do not restart the web relay unless the user has explicitly given permission.
If a manual restart is requested, use POST /api/relay/shutdown only.
Do not run tests that spawn Copilot CLI clients unless the user explicitly permits it.
In extension-managed mode, polling begins after the CLI session becomes active (typically after the first prompt).
The extension now supervises managed server.js restarts (bounded backoff) while the CLI session is alive, and stops restart attempts on session shutdown.
When the CLI extension connects, it also prints the relay info window (local/network/remote/auth/polling URLs) directly in the Copilot CLI client.
Respawner scripts (start:server:respawn*) are legacy/manual troubleshooting tools only and are not part of the extension-managed startup path.
Do not use them for manual restarts; use POST /api/relay/shutdown instead.
You can install the repo locally and get a global copilot-remote command without publishing:
npm link
# or
npm install -g .Run it from any folder to start the web relay server for that folder's workspace root, then immediately hand the shell to gh copilot without a bootstrap prompt. If a relay is already active, the command reuses it and still opens Copilot in the same shell.
Relay server output is written to a logfile under %LOCALAPPDATA%\copilot-remote\logs by default (or COPILOT_WEB_RELAY_LOG_DIR if you set it), so it stays out of the CLI terminal.
If you want custom token/tunnel settings from a specific server/config.json, point COPILOT_WEB_RELAY_CONFIG at that file before launching. A plain npm install -g . does not bundle the repo-local gitignored config file.
Manual relay shutdowns are queued via POST /api/relay/shutdown and only take effect after the current turn goes idle, so they are not a way to interrupt a turn in progress.
Roadmap for later launcher modes:
- Option 2: launch/attach a Copilot CLI session directly.
- Option 3: support
copilot-remote -- [gh copilot args]pass-through. - Session resume: add
--session-id=<...>handoff once the session orchestration contract is defined.
- Choose mode and model per message in the composer.
- Use Compact to branch to a fresh conversation seeded with summary context.
- Use Browse files to inspect workspace/drives and open previews.
- Click file/folder copy controls to insert
@file:.../@folder:...tokens. - Answer clarification prompts in relay question cards (from
ask_user). - Use the usage button (
📊) for live Copilot usage summary. - Use the Context button to read the latest token/context metrics in a modal from local session-state events.
- Workspace browsing is locked to the Copilot CLI startup CWD (your active repo root) and does not retarget via chat
cd ...commands.
| Mode | Behavior |
|---|---|
ask |
Clarification-first behavior before implementation |
plan |
Planning response style (no implementation unless requested) |
agent |
Interactive coding agent behavior |
autopilot |
Action-first behavior; asks only when truly blocking |
The model picker is fed by live snapshot updates from the active CLI runtime and falls back to a curated set:
claude-sonnet-4.6claude-haiku-4.5gpt-5.4gpt-5.4-minigpt-5.3-codex
Selection is persisted in browser storage and attached per message.
| Key | Default | Description |
|---|---|---|
authToken |
generated if missing | Required for API/UI auth; set explicitly for stable access |
port |
3333 |
HTTP + WebSocket port |
localhostOnly |
true |
Bind only to loopback (127.0.0.1) and disable LAN/WAN access |
pollIntervalMs |
3000 |
CLI heartbeat/poll cadence |
processingTimeoutMs |
600000 |
Max turn processing wait |
ask_user timeout |
900000 |
ask_user question wait; edit shared/question-timeout.mjs to change it |
conversationSessionMode |
isolated |
Configured strategy (isolated / shared) exposed in status |
restartGracefulTimeoutMs |
8000 |
Graceful restart wait before force fallback |
restartShutdownTimeoutMs |
45000 |
Drain timeout while waiting for active queue job completion |
restartSpawnTimeoutMs |
18000 |
Max wait for resume/restart phase per attempt |
restartRebindTimeoutMs |
20000 |
Max wait for rebind/session-sync completion per attempt |
restartMaxAttempts |
3 |
Bounded restart attempts before terminal exhaustion |
restartRetryBackoffMs |
[1000,3000,7000] |
Deterministic retry backoff schedule in milliseconds |
maxRequeueRetries |
5 |
Queue retry limit for failed processing |
remotePath |
"" |
URL base path when reverse-proxied under a subpath |
sshTunnel.enabled |
false |
Enable reverse SSH tunnel |
sshTunnel.remoteBind |
loopback |
Remote bind mode for SSH -R (loopback or public) |
sshTunnel.user |
— | SSH user |
sshTunnel.host |
— | SSH host |
sshTunnel.remotePort |
— | Remote forwarded port |
sshTunnel.identityFile |
optional | SSH key path (falls back to default agent/key) |
Session mismatch recovery is restart-driven: the relay restart orchestrator parks queue work, restarts/rebinds the CLI runtime, and resumes dequeueing after rebind confirmation. The extension no longer attempts in-process session switch APIs from the dequeue/send path.
Configure:
"sshTunnel": {
"enabled": true,
"remoteBind": "loopback",
"user": "ubuntu",
"host": "relay.example.com",
"remotePort": 4444,
"identityFile": "~/.ssh/id_rsa"
}localhostOnly controls only the local relay listener (127.0.0.1 vs 0.0.0.0).
SSH tunnel exposure is controlled independently by sshTunnel.remoteBind.
Then reverse proxy on the VPS (example Caddy):
relay.example.com {
reverse_proxy localhost:4444
}
The relay auto-reconnects tunnel drops with exponential backoff.
Install extension files for use across repositories:
%USERPROFILE%\.copilot\extensions\web-relay\ (Windows)
~/.copilot/extensions/web-relay/ (Linux/macOS)
Useful environment variables:
COPILOT_WEB_RELAY_SERVER_DIR(recommended)COPILOT_WEB_RELAY_ROOTCOPILOT_WEB_RELAY_CONFIGCOPILOT_WEB_RELAY_TOOLSCOPILOT_WEB_RELAY_LOG_DIRCOPILOT_WEB_RELAY_NODE
Project-local extension files still take precedence when both exist.
If the same extension is available both project-local (.github/extensions/web-relay/) and user-global (~/.copilot/extensions/web-relay/), Copilot may show duplicates in extension management. Keep only one active copy to avoid double-loading.
Common routes:
- Browser/API:
/api/message,/api/conversations,/api/conversation/:id,/api/status,/api/models,/api/usage - CLI bridge:
/api/pending,/api/response,/api/activity,/api/heartbeat - Questions:
/api/relay-question,/api/relay-question/:id,/api/relay-question/:id/answer - File access:
/api/files/*,/api/files-preview/*,/api/repo/tree,/api/drives/* - Uploads:
/api/upload,/api/upload/:sha256/content
All authenticated routes accept either:
Authorization: Bearer <token>- auth cookie from prior login
For deeper implementation/API details, see server/README.md.
| Symptom | What to check |
|---|---|
| UI says CLI offline | Send one CLI prompt to trigger extension session start, then check /api/status |
| Messages stuck pending | Ensure only one relay owner is running and only one process owns port 3333 |
| Wrong/old model shown | Check /api/models and extension logs for model snapshot updates |
| Clarification card not progressing | Answer via the web card; relay resumes after question status becomes answered |
| File links fail | Verify auth token/cookie and that paths are inside allowed workspace/drive roots |
- Auth is token-based and enforced on API + Socket.IO.
- Successful auth sets an HttpOnly cookie for browser sessions.
- Keep
server/config.jsonprivate and rotateauthTokenif exposed. - Set
localhostOnlytotrueto force local-only access (no LAN/WAN listener). - If exposed beyond LAN, use HTTPS and a strong token.
copilot-remote/
├── .github/extensions/web-relay/ # Copilot CLI extension (polling, ask_user bridge, model snapshotting)
├── server/ # Express + Socket.IO relay server and web app
├── docs/ # Project planning notes
└── README.md
More views from the same app experience:
Mobile (PWA/Browser) file viewer showing an image preview and download actions.
|