A canvas extension for the GitHub Copilot app that embeds a GitHub Codespace directly in a Copilot side panel.
Open a picker of all your codespaces, or jump straight into a specific one — without leaving the Copilot app.
- Picker mode — lists your codespaces (name, repository, branch, state, last used) with an Open button for each, plus New codespace and Refresh.
- Direct mode — open a specific codespace editor by name (one-time github.com sign-in in the webview, then it persists), or load an explicit URL.
- Sign-in-free editor (
editorServe) — seteditorServe: trueto get a full VS Code editor with no github.com sign-in at all. The extension runscode serve-webinside the codespace (authenticated by a per-open connection token in the URL, not a github.com cookie), exposes its port, and loads the tokenized URL. Opens in a dark theme by default (seeded once; your later theme change sticks). Requires the devcontainersshdfeature (see below). - Public port (auth-free) — set
publicPortto expose a codespace port with public visibility and load its real GitHub browse URL. Shareable and requires no sign-in.⚠️ Anyone with the URL can reach it. - Private forward (auth-free) — set
remotePortto forward a codespace port to loopback (127.0.0.1) over the app'sghlogin. No sign-in, private to your machine. - Start the app for you (
startCommand) — combined withpublicPort/remotePort, runs a command inside the codespace overghto launch the app first, then previews it. No web editor at all — requires the devcontainersshdfeature (see below). - Agent actions — the Copilot agent can call:
list_codespaces— returns your codespaces as JSON (optionally filtered by repo).get_current— reports what the panel is currently showing (mode, url, ports, browseUrl).exec_in_codespace— run any shell command inside a codespace overgh(auth-free);background: trueto start a long-running server. Requires thesshdfeature.make_port_private— revert apublicPortexposure back to private.refresh— reload the canvas; for the sign-in-free editor it repairsserve-web/ the port forward if they died, orhard: truerestartsserve-webwith a fresh token. Re-open the canvas afterward to reload the panel.stop_forward— stop a privateremotePortforward for the instance.
startCommand, editorServe, and exec_in_codespace run commands via
gh codespace ssh, which needs an SSH server in the container. Add the
feature to your repo's .devcontainer/devcontainer.json and rebuild (or create)
the codespace:
With that in place, the whole preview flow is fully auth-free — the extension
starts your app and exposes the port using only the app's gh login, and you
never open the web editor or hit a 2FA prompt. Without sshd, use publicPort/
remotePort on a port you started yourself (e.g. from the editor terminal).
Heads up on existing codespaces: a codespace created before you added
sshdwon't pick it up from a rebuild — the rebuild reads the devcontainer config already inside the container, not the latest branch. Create a fresh codespace on the updated branch instead.
There are two separate auth surfaces:
ghAPI calls (listing codespaces, forwarding ports, changing visibility) use yourghCLI login. ThepublicPortandremotePortfeatures run entirely over this, so the app authenticates them for you — no sign-in appears in the webview.- The hosted editor (direct mode) is a github.com web app that needs a browser session cookie. That cookie can't be minted from an OAuth token, so the editor asks you to sign in once in the webview; it persists afterward.
If you want a full editor with zero github.com sign-in, use editorServe: true — it runs code serve-web inside the codespace and authenticates with a connection token in the URL instead of a github.com cookie. If you just want to preview a running app with zero webview login, use publicPort (shareable) or remotePort (private) instead of the editor.
GitHub's web editor (vscode.dev / github.com/codespaces) is served with
Content-Security-Policy: frame-ancestors 'none', which means a codespace
cannot be embedded in an <iframe>.
This extension works around that by loading the codespace as the top-level
document of the canvas webview instead of framing it. frame-ancestors
only restricts framing — not top-level navigation — so:
- Direct mode returns the codespace URL as the canvas URL.
- Picker mode serves a small local HTML page (on an ephemeral
127.0.0.1port) whose links perform a top-levelwindow.locationnavigation to the chosen codespace.
Named codespaces open via https://github.com/codespaces/<name>, which handles
auth and redirects to the correct editor host.
Port previews don't need the editor's cookie at all:
remotePortrunsgh codespace ports forward <remote>:<local>to an ephemeral loopback port and loadshttp://127.0.0.1:<local>— private to your machine, no auth.publicPortfirst forwards the port (headless, a listening port isn't auto-detected the way the web editor does it), which registers it and lets the extension probe the app for readiness, then runsgh codespace ports visibility <port>:publicand loads the realbrowseUrlfromgh codespace ports --json(never guessed). Public ports serve without any auth.startCommand(optional) runsgh codespace ssh -c <name> -- <command>detached to launch the app before either preview. This needs thesshdfeature; the port previews themselves do not.
The port previews rely on the Codespaces port-forwarding service. The app must be
listening on the port inside the codespace — either started by you, or by
startCommand.
-
The GitHub Copilot app with canvas-extension support.
-
The
ghCLI installed and authenticated. -
For listing codespaces and the auth-free port previews (
publicPort/remotePort), theghtoken needs thecodespacescope:gh auth refresh -h github.com -s codespace
Direct-open by codespace name or URL works without this scope.
Ask Copilot to install it from this repository folder:
Install the extension from https://github.com/arilivigni/codespace-canvas/tree/main/codespace-canvas
Or use the install_extension tool directly with that URL.
| Scope | Effect |
|---|---|
user (default) |
Available in all your projects |
project |
Installed into the current repo's .github/extensions/ |
session |
Scoped to a single session only |
Once installed, ask Copilot to open the canvas, e.g.:
- "Open the codespaces canvas" → shows the picker.
- "Open codespace
octocat-myrepo-abc123sign-in free" →editorServe(full editor, no github.com sign-in; needssshd). - "Open codespace
octocat-myrepo-abc123" → opens that codespace's hosted editor directly (one-time github.com sign-in). - "Preview port 3000 of my codespace publicly" →
publicPort(auth-free, shareable). - "Forward port 3000 of my codespace privately" →
remotePort(auth-free, local only). - "Run my dev server and preview it" →
startCommand+publicPort/remotePort(auth-free, no editor; needssshd). - "Refresh the codespace canvas" →
refreshaction (re-open the canvas to reload the panel).
All modes below target a codespace you already have by codespaceName. Pick
one based on what you want:
| You want… | Open input | Auth in the panel | Needs sshd? |
|---|---|---|---|
| Full editor, no sign-in | { codespaceName, editorServe: true } |
connection token in the URL | ✅ yes |
| Real hosted editor | { codespaceName } |
one-time github.com sign-in | no |
| Preview a running app (shareable) | { codespaceName, publicPort: 3000 } |
app's gh login |
no* |
| Preview a running app (private) | { codespaceName, remotePort: 3000 } |
app's gh login |
no* |
| Start the app, then preview | + startCommand: "npm run dev" |
app's gh login |
✅ yes |
| Just browse / pick | (no input) → picker | — | no |
* The publicPort / remotePort previews need the app already listening on
that port; startCommand and editorServe start things for you and so need sshd.
How editorServe attaches, under the hood: finds the codespace by name →
runs code serve-web inside it over gh codespace ssh (auth is a per-open
connection token in the URL, not a github.com cookie) → seeds a dark theme →
forwards and exposes the port → loads https://<name>-8200.app.github.dev/?tkn=….
The canvas id is codespace-canvas. Open input:
{
"codespaceName": "octocat-myrepo-abc123", // target an existing codespace by name
"editorServe": true, // full VS Code editor, NO github.com sign-in (needs sshd)
"repo": "owner/repo", // filter picker + target "New codespace"
"url": "https://...", // load an explicit URL
"publicPort": 3000, // expose port publicly + load its GitHub URL (auth-free, shareable)
"remotePort": 3000, // forward port to 127.0.0.1 (auth-free, private)
"startCommand": "npm run dev" // start the app in the codespace first (needs sshd)
}editorServe, publicPort, and remotePort all require codespaceName.
publicPort / remotePort also need an app listening on that port — either
started by you or by startCommand.
The extension is a single ES module: codespace-canvas/extension.mjs.
node --check codespace-canvas/extension.mjs # syntax checkAfter editing, reload extensions in Copilot (extensions_reload) to pick up changes.