Skip to content

Add GitHub Codespaces ingress to the Windows shell - #410

Merged
coneilen merged 3 commits into
mainfrom
coneilen-microsoft-windows-codespaces-ingress-parity
Sep 22, 2026
Merged

coneilen merged 3 commits into
mainfrom
coneilen-microsoft-windows-codespaces-ingress-parity

Conversation

@coneilen

@coneilen coneilen commented Sep 22, 2026

Copy link
Copy Markdown
Collaborator

TDD evidence

RED: zig test src\GraphModel.zig -> FAIL "project identity derives remote and global from Codable paths" at GraphModel.zig:1531, a codespace:// project is not reported as remote (56 passed; 1 failed)
GREEN: zig test src\GraphModel.zig -> All 57 tests passed
REGRESSION: pwsh Tools\windows\Tests\WindowsShell.Tests.ps1 -> All 96 tests passed, "Windows shell scaffold contract: PASS", exit 0

A second RED was taken at the integration point, by reverting only App.zig, MainWindow.zig and InputRouter.zig to their pre-change state while keeping the new harness contracts:

RED: pwsh Tools\windows\Tests\WindowsShell.Tests.ps1 -> throws "Windows shell contract: the Add Folder menu must offer codespace ingress next to the other repository sources"
GREEN: pwsh Tools\windows\Tests\WindowsShell.Tests.ps1 -> All 96 tests passed, scaffold contract PASS

Both REDs are behavior failures of the missing capability, not toolchain or fixture errors: the suites compile and run, and every other test in them passes.

What

Windows could open a local folder, clone over HTTPS, and attach an SSH remote. A codespace - the fourth source the macOS welcome flow has always offered (CodespaceFormView / WelcomeFeature / CodespaceClient) - had nowhere to go. This adds it with the same shape: discover, choose, validate, open. Existing local/clone/SSH ingress is untouched.

How

graphcode-windows/src/Codespaces.zig - the client half, pure logic so it tests without a window:

  • locates gh.exe (GRAPHCODE_GH override, Program Files, WinGet Links/Programs, then PATH)
  • gh codespace list --limit 500 --json name,displayName,repository,state, parsed tolerantly (malformed rows dropped, unknown state kept as gh words it)
  • classifies the failures gh actually returns - missing CLI, unauthenticated, missing codespace scope, no network, unreadable list - each into a remediation a human can act on
  • redacts any gho_/ghp_/ghu_/ghs_/ghr_/github_pat_ run, plus control characters, out of gh output before it can reach a status line
  • builds the validation argv and the codespace:// project URI
  • persists only the codespace name and repository path to %LOCALAPPDATA%\GraphCode\codespace.ini

Validation dials gh codespace ssh with BatchMode=yes so it can never block on a prompt, and runs git rev-parse --show-toplevel in the chosen path, so an accepted identity is one whose sessions can actually start.

graphcode-windows/src/WindowsCodespaceDialog.zig - the sheet. Its state lives in a Model that is exercised directly, so loading, empty-account (create link instead of retry), failure plus retry, in-flight validation, cancel-while-dialing, inline-failure clearing, and submit gating are all covered without driving Win32. Selecting a codespace prefills its default workspace path and never overwrites a human edit.

The window is standard controls with dark WM_ERASEBKGND/WM_CTLCOLOR* painting, a STATIC label before each control in z-order so UIA names them, IsDialogMessageW for tab, and Escape to cancel - no custom provider, so the accessibility harness sees a real control tree.

Daemon protocol: no change needed. Checked first. RemoteProjectLocation already parses codespace:// and already dials through the GitHub CLI, so an accepted codespace opens through the same openProject call every other source uses. The only portable addition is Windows gh.exe candidates in GhLocator, which otherwise listed macOS paths only while still compiling into the Windows target.

Also: Project.isRemote() now accepts codespace:// so it groups with remotes rather than as a filesystem path; and the recent-folders submenu is located rather than indexed, because adding this menu item is precisely what would have silently retargeted that rebuild at the wrong item.

Credential handling

No token or device-code secret is logged, echoed, persisted, or committed anywhere in this change. gh owns the credential store; this code never reads it. Every byte of gh output passes through sanitizeMessage, which strips token-shaped runs and control characters before it can reach a status line, and that stripping is asserted by test. The only persisted state is the codespace name and repository path. The single token-shaped literal in the tree is a synthetic redaction fixture (gho_EXAMPLENOTAREALTOKEN0000), labelled as such, and exists only so the redactor can be proven to remove it.

Validation status - read honestly

Verified, with output quoted above: the client logic, the dialog state model, the menu/command/ingress wiring, codespace:// project identity, the harness contracts, and a clean zig build.

Not verified: a real end-to-end Codespaces connect. The token available here lacks the codespace scope:

gh codespace list
error getting codespaces: HTTP 403 ...
This API operation needs the "codespace" scope. To request it, run:
  gh auth refresh -h github.com -s codespace

So the failure path is exercised for real against live gh, but discovery-success, selection against a real list, and a real validated dial are not exercised and are not claimed to be. This needs a reviewer with a codespace-scoped token to confirm. The parity ledger should record Codespaces connect as unverified pending that, not as validated.

Separately and unrelated to this change: the Swift half of the suite cannot build on this machine (error: missing required modules: '_complex', 'ucrt', and vcvars64.bat reports VsDevCmd.bat encountered errors). It fails compiling MailroomKit, which this PR does not touch. The Swift sources here parse clean under the 6.3.3 toolchain; the new XCTest in windows-tests/ needs CI to execute.

@coneilen
coneilen force-pushed the coneilen-microsoft-windows-codespaces-ingress-parity branch 2 times, most recently from c107bac to 036939a Compare September 22, 2026 18:39
coneilen and others added 3 commits September 22, 2026 12:05
The Windows shell could open a local folder, clone over HTTPS, and attach an
SSH remote, but a codespace - the source the macOS welcome flow has always
offered - had nowhere to go. This adds the fourth ingress with the same shape
as the macOS sheet: discover, choose, validate, then open.

Codespaces.zig is the client half and is pure logic, so it is testable without
a window: it locates gh.exe, asks it for the account's codespaces as JSON,
classifies the failures gh actually returns (missing CLI, unauthenticated,
missing codespace scope, no network) into remediations a human can act on,
redacts any token-shaped run out of gh output before it can reach a status
line, and builds both the validation argv and the codespace:// project URI.
Validation dials `gh codespace ssh` with BatchMode so it can never block on a
prompt, and runs `git rev-parse` in the chosen path so an accepted identity is
one whose sessions can actually start.

WindowsCodespaceDialog.zig is the sheet. Its state lives in a Model that is
exercised directly, so loading, empty, failure/retry, in-flight validation,
cancel-while-dialing, and submit gating are all covered without driving Win32.
The window itself is standard controls with dark WM_CTLCOLOR* painting, a
STATIC label before each control so UIA names them, and IsDialogMessageW for
tab and escape - no custom provider, so the existing accessibility harness
sees a real control tree.

No daemon protocol change was needed. RemoteProjectLocation already parses
codespace:// and already dials through gh, so an accepted codespace opens via
the same openProject call as every other source; GhLocator only lacked Windows
paths for gh.exe, which is the one portable addition here.

The recent-folders submenu is now located rather than indexed, because adding
this menu item is exactly what would have silently retargeted that rebuild.

Signed-off-by: Colin Neilens <coneilen@microsoft.com>

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The sanitizer test needs token-shaped input to prove it strips it, but a
realistic-looking literal is the kind of thing a scanner flags and a reviewer
has to stop and verify. Only the prefix and the shape matter to the redactor,
so the fixture now says what it is.

Signed-off-by: Colin Neilens <coneilen@microsoft.com>

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The row says exactly what the deterministic suite proves and exactly what it
does not: a live connect was never exercised, because the available token has
no codespace scope, so only the 403 path met real gh. Calling that Validated
would make the ledger a worse signal than no row at all.

Also notes that the Add Folder submenu is now located rather than indexed.

Signed-off-by: Colin Neilens <coneilen@microsoft.com>

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@coneilen
coneilen force-pushed the coneilen-microsoft-windows-codespaces-ingress-parity branch from 036939a to 3cd8328 Compare September 22, 2026 19:07
@coneilen
coneilen merged commit a1eabc5 into main Sep 22, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant