Skip to content

Fix Inline VS Code auth/Settings Sync persistence across reloads - #6841

Closed
austinywang wants to merge 13 commits into
mainfrom
issue-6595-inline-vs-code-loses-settings-sync
Closed

austinywang wants to merge 13 commits into
mainfrom
issue-6595-inline-vs-code-loses-settings-sync

Conversation

@austinywang

@austinywang austinywang commented Jun 26, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #6595

Problem

Inline VS Code (Open Current Directory in VS Code (Inline)) launched VS Code Web through the cached ~/.vscode/cli/serve-web/<id>/bin/code-server binary directly when available. That bypasses VS Code's code-tunnel serve-web wrapper, which sets up the CLI secret-storage/keyring path VS Code Web uses to persist GitHub auth and Settings Sync. Combined with an ephemeral --port 0 and a fresh per-launch temporary connection-token, the inline server identity changed on every launch — so signing into GitHub / Settings Sync inside Inline VS Code was lost on pane reload, folder change, or app relaunch (extensions persisted while auth/settings did not).

Fix

Launch through the wrapper and stabilize the server identity:

  • Prefer code-tunnel serve-web; fall back to the cached code-server only when the wrapper is unavailable (VSCodeServeWebLauncherKind).
  • Enable the CLI file keyring for wrapper launches (VSCODE_CLI_USE_FILE_KEYRING=1) and pin VSCODE_CLI_DATA_DIR.
  • Stable serve-web server data dir under Application Support (per bundle id) with user-data / cli-data subdirs.
  • Stable, persisted port (deterministic per-bundle default stored under vscodeServeWeb.port), with an ephemeral-port fallback if the stable port can't be bound.
  • Reuse a persistent connection-token file (validated 32-hex, 0600) instead of a fresh temporary token per launch, so the server URL — and the browser session keyed to it — stays stable. stop() no longer deletes the token.
  • Omit the unsupported --user-data-dir for the wrapper; keep it for the cached code-server fallback.
  • Developer escape hatches: CMUX_VSCODE_SERVE_WEB_DATA_DIR, CMUX_VSCODE_SERVE_WEB_PORT (user-facing config for serve-web options is a separate follow-up per the issue).

Tests

Added/updated unit tests in cmuxTests/OmnibarAndToolsTests.swift (already wired into the test target):

  • Wrapper-preferred-over-cached-code-server + fallback selection, asserting launcherKind.
  • VSCodeServeWebRuntimeLocator: stable path layout, server-data/cli-data env overrides, deterministic+persisted port, env port override, invalid-override handling, per-bundle distinct ports.
  • VSCodeConnectionToken/VSCodeConnectionTokenStore: 32-hex validation, generation, file create with 0600, reuse-if-valid, replace-if-invalid.
  • VSCodeServeWebLaunchOptionsBuilder: wrapper enables keyring + omits --user-data-dir; cached code-server includes --user-data-dir and no keyring env; ephemeral fallback port reflected in args.

Notes

  • Localization audit: no user-facing strings changed (only CLI args, env-var keys, and file paths); command palette title/keywords unchanged — nothing to add to Localizable.xcstrings or web message catalogs.
  • Swift file-length budget refreshed for the two touched files only (scripts/swift_file_length_budget.py canonical format).
  • VS Code is not installed on the build host, so wrapper CLI flags follow the issue spec + VS Code CLI semantics; runtime verification is covered by the issue's live testing and the unit suite.

🤖 Generated with Claude Code


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Summary by cubic

Fixes Inline VS Code losing GitHub auth and Settings Sync across reloads by launching VS Code Web via code-tunnel serve-web, stabilizing server identity (data dirs, token, and a persisted stable port with deterministic alternates), and tightening startup/restart handling. Fixes #6595.

  • Bug Fixes

    • Prefer code-tunnel serve-web; fall back to cached code-server only if the wrapper is missing.
    • Persist identity: file keyring (VSCODE_CLI_USE_FILE_KEYRING=1), stable CLI/server data dirs (0700), and a 0600 --connection-token-file (validated 32-hex).
    • Stable port: persist a per-bundle port; try deterministic alternates first; use 0 only as a last resort; persist the bound port.
    • Launch/stop flow: omit --user-data-dir for the wrapper; wait for termination; defer relaunch until stop completes; relaunch after forced stop; retry only on detected port collisions.
    • Readiness: output collector waits for “Web UI available at …”, detects port collisions from logs, and enforces a startup timeout.
  • Refactors

    • Extracted the serve-web subsystem into dedicated files and updated project wiring/file-length budget.
    • Removed the debug factory; tests inject a launch override via the controller initializer; expanded tests for launcher selection, stable paths/ports and overrides, token reuse/replace and perms, per-launcher args/env, restart/forced-stop relaunch, collision-only retry behavior, and startup readiness/timeout.

Written for commit da16c78. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added support for launching and managing VS Code Web’s serve-web experience.
    • Improved launcher selection so the app can use the wrapper when available, or fall back to a cached server when needed.
    • Added more reliable port and connection-token handling for stable reconnects and safer startup behavior.
  • Bug Fixes

    • Better handling of startup, restart, and shutdown flows for running VS Code Web sessions.
    • Strengthened validation of generated connection tokens and launch settings.

Inline VS Code ("Open Current Directory in VS Code (Inline)") launched VS
Code Web through the cached ~/.vscode/cli/serve-web/<id>/bin/code-server
binary directly when available. That bypasses VS Code's `code-tunnel
serve-web` wrapper, which sets up the CLI secret-storage/keyring path VS
Code Web uses to persist GitHub auth and Settings Sync. Combined with an
ephemeral `--port 0` and a fresh per-launch temporary connection token,
the inline server identity changed on every launch, so signing in inside
Inline VS Code was lost on pane reload, folder change, or app relaunch.

Launch through the wrapper and stabilize the server identity:

- Prefer `code-tunnel serve-web`; fall back to the cached `code-server`
  only when the wrapper is unavailable (VSCodeServeWebLauncherKind).
- Enable the CLI file keyring for wrapper launches
  (VSCODE_CLI_USE_FILE_KEYRING=1) and pin VSCODE_CLI_DATA_DIR.
- Use a stable serve-web server data dir under Application Support
  (per bundle id), with user-data and cli-data subdirs.
- Use a stable, persisted port (deterministic per-bundle default stored
  under vscodeServeWeb.port), with an ephemeral-port fallback if the
  stable port can't be bound.
- Reuse a persistent connection-token file (validated 32-hex, 0600)
  instead of a fresh temporary token per launch, so the server URL — and
  the browser session keyed to it — stays stable. stop() no longer
  deletes the token.
- Omit the unsupported `--user-data-dir` for the wrapper; keep it for the
  cached code-server fallback.
- Env overrides: CMUX_VSCODE_SERVE_WEB_DATA_DIR, CMUX_VSCODE_SERVE_WEB_PORT.

Adds unit tests for wrapper preference + launcher kind, stable
path/port resolution and env overrides, persistent connection-token
validate/reuse/replace, and per-launcher argument/environment shaping.

Fixes #6595

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 26, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jul 5, 2026 3:29am
cmux-staging Building Building Preview, Comment Jul 5, 2026 3:29am

@coderabbitai

coderabbitai Bot commented Jun 26, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR extracts VS Code serve-web support into a dedicated source file, updates project wiring, and adds launcher, runtime, token, controller, and test coverage for the inline VS Code launch flow.

Changes

VS Code serve-web support extraction

Layer / File(s) Summary
Launcher selection and extraction
Sources/App/TerminalDirectoryOpenSupport.swift, Sources/App/VSCodeServeWebSupport.swift, cmux.xcodeproj/project.pbxproj, cmuxTests/OmnibarAndToolsTests.swift
Moves the serve-web URL and launcher selection logic into VSCodeServeWebSupport.swift, removes the old block from TerminalDirectoryOpenSupport.swift, adds the new source to the Xcode project, and updates launcher-selection tests for wrapper preference and cached fallback.
Runtime, tokens, and launch options
Sources/App/VSCodeServeWebSupport.swift, cmuxTests/OmnibarAndToolsTests.swift
Adds runtime directory and port resolution, connection-token persistence, launch-option translation, and tests covering runtime location, port selection, token validity, and argument and environment shaping.
Controller lifecycle and process loop
Sources/App/VSCodeServeWebSupport.swift
Adds the singleton controller, queued completions, stop and restart handling, process launch, output collection, timeout handling, and loopback-origin URL validation for serve-web.

Sequence Diagram(s)

sequenceDiagram
  participant EnsureServeWebURL
  participant VSCodeServeWebController
  participant VSCodeServeWebLaunchOptionsBuilder
  participant Process
  participant ServeWebOutputCollector

  EnsureServeWebURL->>VSCodeServeWebController: ensureServeWebURL(vscodeApplicationURL:completion:)
  VSCodeServeWebController->>VSCodeServeWebLaunchOptionsBuilder: build launch options
  VSCodeServeWebController->>Process: launch serve-web
  Process->>ServeWebOutputCollector: stream stdout/stderr
  ServeWebOutputCollector-->>VSCodeServeWebController: Web UI URL
  VSCodeServeWebController-->>EnsureServeWebURL: completion(URL)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • Issue 6645: The wrapper preference, stable data directories, persisted port, and connection-token changes align with the inline VS Code auth and settings persistence problem.

Possibly related PRs

  • manaflow-ai/cmux#5595: It also changes the inline VS Code serve-web path, URL detection, and launch-argument selection.

Suggested reviewers

  • lawrencecchen

Poem

A rabbit hopped through serve-web’s glow,
With stable tokens set just so.
The tunnel wrapper led the way,
And auth stayed snug from day to day.
🐇✨


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (6 errors, 1 warning)

Check name Status Explanation Resolution
Cmux Swift Blocking Runtime ❌ Error Sources/App/VSCodeServeWebSupport.swift adds NSLock, DispatchSemaphore, and waitForURL(timeout) in production, which the repo rule forbids for non-test Swift. Refactor the collector/controller to use an actor or async signal/AsyncStream, and remove blocking waits and manual locks from Sources/App; keep any blocking scaffolding test-only.
Cmux Algorithmic Complexity ❌ Error ServeWebOutputCollector.append() rescans the growing stdout/stderr buffer with firstIndex(where:) on each chunk, a repeated full scan in a process-output path. Track a parse cursor or handle only the newly appended chunk (keeping one trailing partial line) so each byte is scanned once instead of rescanning the whole buffer.
Cmux Swift File And Package Boundaries ❌ Error New 915-line Sources/App file mixes parsing, token/port persistence, launch config, and process control, breaching the file-boundary rule. Split the serve-web logic into a small SwiftPM package (parsing/runtime/token/launch/controller), leaving only thin app glue in Sources/App.
Cmux Architecture Rethink ❌ Error FAIL: production ServeWebOutputCollector uses NSLock and DispatchSemaphore.wait(timeout:) to block on process output, which the architecture rule forbids. Refactor URL discovery to an actor or async signal (e.g. AsyncStream/CheckedContinuation) and remove manual locks/semaphores from Sources/App/VSCodeServeWebSupport.swift.
Cmux No Test Or Debug Seam In Production Source ❌ Error Sources/App/VSCodeServeWebSupport.swift still exposes #if DEBUG static func makeForTesting, and tests call it directly. Remove the test hook, widen VSCodeServeWebController init to internal, and instantiate it from tests via @testable import.
Cmux No Ambient Global State ❌ Error VSCodeServeWebSupport adds VSCodeServeWebController.shared, a new runtime singleton used by AppDelegate/ContentView/routing, which the no-ambient-global-state rule forbids. Remove shared and create VSCodeServeWebController at the app seam, then inject it into callers and tests instead of accessing global runtime state.
Docstring Coverage ⚠️ Warning Docstring coverage is 6.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (18 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes match the issue: wrapper-first launch, keyring env, stable data/port/token, cached code-server fallback, and no --user-data-dir for wrapper.
Out of Scope Changes check ✅ Passed The refactor to a new support file and Xcode project updates are directly related to the serve-web change, with no clear unrelated additions.
Cmux Swift Actor Isolation ✅ Passed No new MainActor/Sendable or UI-store isolation regressions were introduced; the new mutable helpers are internally queue/lock-synchronized, and actor usage is unchanged.
Cmux Browser Automation Off-Main ✅ Passed PASS: The diff only touches ghostty selection-row APIs; no browser.* socket commands, worker-router changes, or policy-test gaps appear in the changed files.
Cmux Expensive Synchronous Load ✅ Passed No agent-history or main-actor sync load was added; the new file only moves existing VS Code launch-path filesystem lookups.
Cmux Cache Substitution Correctness ✅ Passed PASS: cached reads are only fallback launch discovery; persisted port/token paths validate and refresh on cold or stale data, so no unchecked cache substitution in a persistence path.
Cmux No Hacky Sleeps ✅ Passed PR changes are Swift-only plus pbxproj; no non-Swift runtime code introduces sleeps/timers/polling.
Cmux Swift Concurrency ✅ Passed PASS: The new queue/DispatchQueue code is for Process/FileHandle/AppKit callback boundaries; no Combine or fire-and-forget Task usage was introduced.
Cmux Swift @Concurrent ✅ Passed The added Swift code has no async/nonisolated/@Concurrent declarations; heavy work is dispatched via GCD queues, so the rule isn’t triggered.
Cmux Swiftpm Lockfiles ✅ Passed PR only adds source-file wiring; no packageReferences or Package.resolved diffs, and no cmux-owned .gitignore now ignores Package.resolved.
Cmux Swift Logging ✅ Passed No added print/debugPrint/dump/NSLog, Logger misuse, or ad hoc production logging appears in the touched Swift diff.
Cmux User-Facing Error Privacy ✅ Passed No production user-facing errors/alerts were added; the diff only adds internal CLI/env/file-path logic and tests/docs comments.
Cmux Full Internationalization ✅ Passed No new user-facing copy or locale resources were added; changes are code, tests, comments, and config tokens only.
Cmux Swiftui State Layout ✅ Passed PASS: The PR only adds non-SwiftUI serve-web support and tests; no ObservableObject, @Published, GeometryReader, or render-time state mutation appears in touched source.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR only adds serve-web support/tests; no NSWindow/NSPanel/WindowGroup code or cmuxAuxiliaryWindowIdentifiers changes, so the auxiliary-window shortcut rule isn’t implicated.
Cmux Source Artifacts ✅ Passed Changed paths are source, tests, and Xcode project config only; no artifact, temp, cache, build, or log directories were added.
Title check ✅ Passed The title matches the main change: preserving Inline VS Code auth and Settings Sync across reloads.
Description check ✅ Passed It clearly explains the problem, fix, and testing, but it doesn't follow the repository's full template sections.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-6595-inline-vs-code-loses-settings-sync

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jun 26, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes Inline VS Code losing GitHub auth and Settings Sync across reloads by replacing the direct code-server binary launch with code-tunnel serve-web and stabilising the server identity (stable data dirs, persisted port, reused connection-token file).

  • Launcher switch: VSCodeCLILaunchConfigurationBuilder now prefers the code-tunnel wrapper (which wires up the CLI keyring) and falls back to the cached code-server only when the wrapper is absent; VSCodeServeWebLaunchOptionsBuilder shapes args/env per VSCodeServeWebLauncherKind (keyring env and --user-data-dir handled correctly per launcher).
  • Stable identity: connection-token is persisted as a 32-hex file (0600) under Application Support/<bundle-id>/vscode-serve-web/ and reused across launches; stop() no longer deletes it; port is persisted to UserDefaults after first successful bind, with deterministic per-bundle FNV-1a derivation as the default and up to 8 stable alternates before falling back to ephemeral port 0.
  • Lifecycle hardening: VSCodeServeWebController adds a termination-wait/barrier with a DispatchSourceTimer SIGKILL deadline so restart() correctly waits for old processes to exit before relaunching; deferred relaunch requests queue behind the barrier.

Confidence Score: 5/5

Safe to merge. The lifecycle management, token persistence, port-retry loop, and termination barrier are all well-guarded and backed by the new unit suite.

All critical paths — token reuse/replacement, port collision retry, process termination sequencing, and deferred-relaunch ordering — are correctly guarded by generation counters and the serial dispatch queue. The old #if DEBUG test seam was cleanly removed and replaced with an internal init for @testable injection, matching the canonical fix pattern. File sizes are within budget across all new files. No new blocking primitives are introduced on main-actor paths.

No files require special attention.

Important Files Changed

Filename Overview
Sources/App/VSCodeServeWebController.swift New file (597 lines, in budget) housing the refactored controller: proper termination wait/barrier with deadline SIGKILL, deferred-relaunch queue, lifecycle-generation guards, and removal of the old #if DEBUG test seam in favour of an internal init for @testable injection.
Sources/App/ServeWebOutputCollector.swift Extracted from TerminalDirectoryOpenSupport; adds port-collision scan tail to detect EADDRINUSE in output so the launch loop can retry on a different port. Semaphore usage carried forward from the old implementation.
Sources/App/VSCodeConnectionTokenStore.swift New file managing the persisted connection-token file: reads and reuses a valid 32-hex token, replaces invalid/tampered files, creates with O_CREAT
Sources/App/VSCodeServeWebRuntimeLocator.swift New file resolving stable paths and port: deterministic per-bundle FNV-1a port derivation, env/persisted-port overrides, and candidateStablePorts generation for the retry loop.
Sources/App/VSCodeCLILaunchConfigurationBuilder.swift Extracted from TerminalDirectoryOpenSupport; launcher preference now code-tunnel wrapper first, cached code-server as fallback, adds launcherKind field to the config struct.
Sources/App/VSCodeServeWebLaunchOptionsBuilder.swift New file shaping arguments and environment per launcher kind: wrapper enables VSCODE_CLI_USE_FILE_KEYRING and pins VSCODE_CLI_DATA_DIR, omits --user-data-dir; cached server path includes --user-data-dir and no keyring env.
Sources/App/TerminalDirectoryOpenSupport.swift Trimmed from 1241 to ~342 lines by extracting the serve-web subsystem into dedicated files; now contains only directory-open targets and workspace shortcut mapping.
cmuxTests/OmnibarAndToolsTests.swift Grew from 1499 to 2105 lines with new tests for launcher selection, stable path layout, port derivation/persistence, token reuse/replace/perms, per-launcher args/env, restart/forced-stop, and collision-only retry.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant UI as UI / Caller
    participant Ctrl as VSCodeServeWebController
    participant LQ as launchQueue
    participant Q as queue (serial)
    participant Builder as VSCodeCLILaunchConfigurationBuilder
    participant TokenStore as VSCodeConnectionTokenStore
    participant Proc as code-tunnel serve-web

    UI->>Ctrl: ensureServeWebURL(vscodeAppURL)
    Ctrl->>Q: queue.async check running / enqueue pending
    Q->>LQ: launchQueue.async launchServeWebProcess
    LQ->>Builder: launchConfiguration(vscodeAppURL)
    Builder-->>LQ: VSCodeCLILaunchConfiguration
    LQ->>TokenStore: ensureToken(at connectionTokenFile)
    TokenStore-->>LQ: reused or newly-created 0600 token file
    loop candidateStablePorts + [0]
        LQ->>Q: queue.sync set launchingProcess run process
        Q->>Proc: code-tunnel serve-web --connection-token-file
        Proc-->>LQ: stdout/stderr via ServeWebOutputCollector
        alt URL found in output
            LQ->>Q: queue.async promote to serveWebProcess
            Q->>UI: completion(serveWebURL) on main queue
        else EADDRINUSE detected
            LQ->>Proc: process.terminate()
            LQ->>LQ: retry next candidate port
        else startup timeout or failure
            LQ->>UI: completion(nil)
        end
    end

    UI->>Ctrl: restart(vscodeAppURL)
    Ctrl->>Q: queue.sync bump lifecycleGeneration collect processes
    Q->>Proc: process.terminate() SIGTERM
    Q->>Q: install DispatchSourceTimer SIGKILL deadline
    Proc-->>Q: terminationHandler fires finishTerminationWait
    Q->>Q: finishStopTerminationBarrier drain deferred requests
    Q->>Q: ensureServeWebURL with requiredLifecycleGeneration
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant UI as UI / Caller
    participant Ctrl as VSCodeServeWebController
    participant LQ as launchQueue
    participant Q as queue (serial)
    participant Builder as VSCodeCLILaunchConfigurationBuilder
    participant TokenStore as VSCodeConnectionTokenStore
    participant Proc as code-tunnel serve-web

    UI->>Ctrl: ensureServeWebURL(vscodeAppURL)
    Ctrl->>Q: queue.async check running / enqueue pending
    Q->>LQ: launchQueue.async launchServeWebProcess
    LQ->>Builder: launchConfiguration(vscodeAppURL)
    Builder-->>LQ: VSCodeCLILaunchConfiguration
    LQ->>TokenStore: ensureToken(at connectionTokenFile)
    TokenStore-->>LQ: reused or newly-created 0600 token file
    loop candidateStablePorts + [0]
        LQ->>Q: queue.sync set launchingProcess run process
        Q->>Proc: code-tunnel serve-web --connection-token-file
        Proc-->>LQ: stdout/stderr via ServeWebOutputCollector
        alt URL found in output
            LQ->>Q: queue.async promote to serveWebProcess
            Q->>UI: completion(serveWebURL) on main queue
        else EADDRINUSE detected
            LQ->>Proc: process.terminate()
            LQ->>LQ: retry next candidate port
        else startup timeout or failure
            LQ->>UI: completion(nil)
        end
    end

    UI->>Ctrl: restart(vscodeAppURL)
    Ctrl->>Q: queue.sync bump lifecycleGeneration collect processes
    Q->>Proc: process.terminate() SIGTERM
    Q->>Q: install DispatchSourceTimer SIGKILL deadline
    Proc-->>Q: terminationHandler fires finishTerminationWait
    Q->>Q: finishStopTerminationBarrier drain deferred requests
    Q->>Q: ensureServeWebURL with requiredLifecycleGeneration
Loading

Reviews (12): Last reviewed commit: "Tighten VS Code serve-web startup handli..." | Re-trigger Greptile

Comment on lines +526 to +638
enum VSCodeServeWebRuntimeLocator {
/// Override the serve-web server data directory (absolute path).
static let serverDataDirectoryEnvironmentKey = "CMUX_VSCODE_SERVE_WEB_DATA_DIR"
/// Override the stable serve-web port.
static let portEnvironmentKey = "CMUX_VSCODE_SERVE_WEB_PORT"
/// VS Code CLI data dir env var; honored as-is when already set.
static let cliDataDirectoryEnvironmentKey = "VSCODE_CLI_DATA_DIR"
/// UserDefaults key the resolved default port is persisted under.
static let portUserDefaultsKey = "vscodeServeWeb.port"

/// IANA dynamic/private port range (49152–65535) avoids well-known and
/// registered ports while still giving every bundle a stable default.
private static let minimumPort = 49152
private static let portRangeSize = 16384

static func resolve(
applicationSupportURL: URL,
bundleIdentifier: String,
environment: [String: String],
persistedPort: Int?
) -> (location: VSCodeServeWebRuntimeLocation, portToPersist: Int?) {
let serverDataDirectoryURL = resolveServerDataDirectoryURL(
applicationSupportURL: applicationSupportURL,
bundleIdentifier: bundleIdentifier,
environment: environment
)
let userDataDirectoryURL = serverDataDirectoryURL
.appendingPathComponent("user-data", isDirectory: true)
let cliDataDirectoryURL = resolveCLIDataDirectoryURL(
serverDataDirectoryURL: serverDataDirectoryURL,
environment: environment
)
let connectionTokenFileURL = serverDataDirectoryURL
.appendingPathComponent("connection-token", isDirectory: false)
let (port, portToPersist) = resolvePort(
bundleIdentifier: bundleIdentifier,
environment: environment,
persistedPort: persistedPort
)

return (
VSCodeServeWebRuntimeLocation(
serverDataDirectoryURL: serverDataDirectoryURL,
userDataDirectoryURL: userDataDirectoryURL,
cliDataDirectoryURL: cliDataDirectoryURL,
connectionTokenFileURL: connectionTokenFileURL,
port: port
),
portToPersist
)
}

private static func resolveServerDataDirectoryURL(
applicationSupportURL: URL,
bundleIdentifier: String,
environment: [String: String]
) -> URL {
if let override = environment[serverDataDirectoryEnvironmentKey],
!override.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
return URL(fileURLWithPath: override, isDirectory: true)
}
return applicationSupportURL
.appendingPathComponent(bundleIdentifier, isDirectory: true)
.appendingPathComponent("vscode-serve-web", isDirectory: true)
}

private static func resolveCLIDataDirectoryURL(
serverDataDirectoryURL: URL,
environment: [String: String]
) -> URL {
if let override = environment[cliDataDirectoryEnvironmentKey],
!override.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
return URL(fileURLWithPath: override, isDirectory: true)
}
return serverDataDirectoryURL.appendingPathComponent("cli-data", isDirectory: true)
}

private static func resolvePort(
bundleIdentifier: String,
environment: [String: String],
persistedPort: Int?
) -> (port: Int, portToPersist: Int?) {
if let override = environment[portEnvironmentKey], let parsed = parsePort(override) {
// Env overrides win but are intentionally not persisted as the default.
return (parsed, nil)
}
if let persistedPort, isValidPort(persistedPort) {
return (persistedPort, nil)
}
let derived = derivePort(from: bundleIdentifier)
return (derived, derived)
}

static func parsePort(_ raw: String) -> Int? {
let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)
guard let value = Int(trimmed), isValidPort(value) else { return nil }
return value
}

static func isValidPort(_ port: Int) -> Bool {
(1024...65535).contains(port)
}

/// Deterministic per-bundle default so different (e.g. tagged) builds get
/// distinct, stable ports instead of colliding on one fixed value.
static func derivePort(from bundleIdentifier: String) -> Int {
var hash: UInt64 = 1469598103934665603 // FNV-1a 64-bit offset basis
for byte in bundleIdentifier.utf8 {
hash ^= UInt64(byte)
hash = hash &* 1099511628211 // FNV-1a 64-bit prime
}
return minimumPort + Int(hash % UInt64(portRangeSize))
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Four new caseless-enum static namespaces

VSCodeServeWebRuntimeLocator, VSCodeConnectionToken, VSCodeConnectionTokenStore, and VSCodeServeWebLaunchOptionsBuilder (lines 526, 644, 668, 736) are all caseless enum types whose entire API is static func/static let. The cmux-no-ambient-global-state rule flags exactly this shape — each of these should be a constructable, injectable struct that takes its dependencies (environment, file manager, etc.) through its initializer rather than reaching for ProcessInfo.processInfo, FileManager.default, or UserDefaults.standard directly. Consolidating them into a single new file (e.g. VSCodeServeWebPersistence.swift) would also address the file-size concern below. The pattern is consistent with the pre-existing VSCodeCLILaunchConfigurationBuilder/VSCodeServeWebURLBuilder debt; the fix here is to not extend it further.

Rule Used: Flag new ambient global state in production Swift:... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are pure, stateless transformations (or take FileManager via an injected default) and reach for no ambient globals — the controller injects ProcessInfo.environment, Bundle.main, UserDefaults, and FileManager into them, which keeps them fully unit-testable (see the new tests). They're kept as caseless-enum namespaces to match the established sibling builders in this same domain (VSCodeServeWebURLBuilder, VSCodeCLILaunchConfigurationBuilder) rather than introducing an inconsistent instance/static mix. They've now been moved into the dedicated VSCodeServeWebSupport.swift (99d3aff), which also resolves the file-size concern. The cmux file-organization policy check is now clean for this diff. Happy to convert them to injectable Service types in a follow-up if the team prefers that direction for the whole serve-web cluster.

— Claude Code

Comment on lines +509 to +777
/// Stable on-disk locations + port for the inline serve-web server. Keeping these
/// fixed across launches is what lets VS Code Web's keyring/secret-storage survive
/// reloads, folder changes, and app relaunches (issue #6595).
struct VSCodeServeWebRuntimeLocation: Equatable {
/// `--server-data-dir`: where serve-web keeps its server-side state.
let serverDataDirectoryURL: URL
/// `--user-data-dir` for the cached code-server fallback (the wrapper derives
/// this from `--server-data-dir` and rejects the flag).
let userDataDirectoryURL: URL
/// `VSCODE_CLI_DATA_DIR` for the wrapper's CLI keyring metadata.
let cliDataDirectoryURL: URL
/// `--connection-token-file`: a persisted token so the server URL is stable.
let connectionTokenFileURL: URL
/// Stable serve-web port (or `0` for the ephemeral fallback attempt).
let port: Int
}

enum VSCodeServeWebRuntimeLocator {
/// Override the serve-web server data directory (absolute path).
static let serverDataDirectoryEnvironmentKey = "CMUX_VSCODE_SERVE_WEB_DATA_DIR"
/// Override the stable serve-web port.
static let portEnvironmentKey = "CMUX_VSCODE_SERVE_WEB_PORT"
/// VS Code CLI data dir env var; honored as-is when already set.
static let cliDataDirectoryEnvironmentKey = "VSCODE_CLI_DATA_DIR"
/// UserDefaults key the resolved default port is persisted under.
static let portUserDefaultsKey = "vscodeServeWeb.port"

/// IANA dynamic/private port range (49152–65535) avoids well-known and
/// registered ports while still giving every bundle a stable default.
private static let minimumPort = 49152
private static let portRangeSize = 16384

static func resolve(
applicationSupportURL: URL,
bundleIdentifier: String,
environment: [String: String],
persistedPort: Int?
) -> (location: VSCodeServeWebRuntimeLocation, portToPersist: Int?) {
let serverDataDirectoryURL = resolveServerDataDirectoryURL(
applicationSupportURL: applicationSupportURL,
bundleIdentifier: bundleIdentifier,
environment: environment
)
let userDataDirectoryURL = serverDataDirectoryURL
.appendingPathComponent("user-data", isDirectory: true)
let cliDataDirectoryURL = resolveCLIDataDirectoryURL(
serverDataDirectoryURL: serverDataDirectoryURL,
environment: environment
)
let connectionTokenFileURL = serverDataDirectoryURL
.appendingPathComponent("connection-token", isDirectory: false)
let (port, portToPersist) = resolvePort(
bundleIdentifier: bundleIdentifier,
environment: environment,
persistedPort: persistedPort
)

return (
VSCodeServeWebRuntimeLocation(
serverDataDirectoryURL: serverDataDirectoryURL,
userDataDirectoryURL: userDataDirectoryURL,
cliDataDirectoryURL: cliDataDirectoryURL,
connectionTokenFileURL: connectionTokenFileURL,
port: port
),
portToPersist
)
}

private static func resolveServerDataDirectoryURL(
applicationSupportURL: URL,
bundleIdentifier: String,
environment: [String: String]
) -> URL {
if let override = environment[serverDataDirectoryEnvironmentKey],
!override.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
return URL(fileURLWithPath: override, isDirectory: true)
}
return applicationSupportURL
.appendingPathComponent(bundleIdentifier, isDirectory: true)
.appendingPathComponent("vscode-serve-web", isDirectory: true)
}

private static func resolveCLIDataDirectoryURL(
serverDataDirectoryURL: URL,
environment: [String: String]
) -> URL {
if let override = environment[cliDataDirectoryEnvironmentKey],
!override.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
return URL(fileURLWithPath: override, isDirectory: true)
}
return serverDataDirectoryURL.appendingPathComponent("cli-data", isDirectory: true)
}

private static func resolvePort(
bundleIdentifier: String,
environment: [String: String],
persistedPort: Int?
) -> (port: Int, portToPersist: Int?) {
if let override = environment[portEnvironmentKey], let parsed = parsePort(override) {
// Env overrides win but are intentionally not persisted as the default.
return (parsed, nil)
}
if let persistedPort, isValidPort(persistedPort) {
return (persistedPort, nil)
}
let derived = derivePort(from: bundleIdentifier)
return (derived, derived)
}

static func parsePort(_ raw: String) -> Int? {
let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)
guard let value = Int(trimmed), isValidPort(value) else { return nil }
return value
}

static func isValidPort(_ port: Int) -> Bool {
(1024...65535).contains(port)
}

/// Deterministic per-bundle default so different (e.g. tagged) builds get
/// distinct, stable ports instead of colliding on one fixed value.
static func derivePort(from bundleIdentifier: String) -> Int {
var hash: UInt64 = 1469598103934665603 // FNV-1a 64-bit offset basis
for byte in bundleIdentifier.utf8 {
hash ^= UInt64(byte)
hash = hash &* 1099511628211 // FNV-1a 64-bit prime
}
return minimumPort + Int(hash % UInt64(portRangeSize))
}
}

/// Connection-token format helpers. VS Code Web compares the URL `tkn` query item
/// against this file's contents, so a stable, valid token keeps the server URL —
/// and the browser-side session/cookies keyed to it — consistent across launches.
enum VSCodeConnectionToken {
private static let hexCharacters = Set("0123456789abcdefABCDEF")

static func isValid(_ token: String) -> Bool {
guard token.count == 32 else { return false }
return token.allSatisfy { hexCharacters.contains($0) }
}

/// 128 bits of randomness rendered as 32 lowercase hex characters.
static func generate() -> String {
let hexDigits = Array("0123456789abcdef")
var characters = [Character]()
characters.reserveCapacity(32)
for _ in 0..<16 {
let byte = UInt8.random(in: UInt8.min...UInt8.max)
characters.append(hexDigits[Int(byte >> 4)])
characters.append(hexDigits[Int(byte & 0x0F)])
}
return String(characters)
}
}

/// Reads/creates the persisted connection-token file, reusing a valid existing
/// token (32-hex, owner-only perms) and replacing anything invalid.
enum VSCodeConnectionTokenStore {
@discardableResult
static func ensureToken(at url: URL, fileManager: FileManager = .default) -> String? {
if let existing = readValidToken(at: url, fileManager: fileManager) {
return existing
}
return writeToken(VSCodeConnectionToken.generate(), to: url, fileManager: fileManager)
}

static func readValidToken(at url: URL, fileManager: FileManager) -> String? {
guard let data = try? Data(contentsOf: url),
let raw = String(data: data, encoding: .utf8) else {
return nil
}
let token = raw.trimmingCharacters(in: .whitespacesAndNewlines)
guard VSCodeConnectionToken.isValid(token),
hasOwnerOnlyPermissions(at: url, fileManager: fileManager) else {
return nil
}
return token
}

static func hasOwnerOnlyPermissions(at url: URL, fileManager: FileManager) -> Bool {
guard let attributes = try? fileManager.attributesOfItem(atPath: url.path),
let permissions = attributes[.posixPermissions] as? NSNumber else {
return false
}
// No group/other bits set (e.g. 0600/0400).
return permissions.uint16Value & 0o077 == 0
}

@discardableResult
static func writeToken(_ token: String, to url: URL, fileManager: FileManager) -> String? {
guard let tokenData = token.data(using: .utf8) else { return nil }
try? fileManager.createDirectory(
at: url.deletingLastPathComponent(),
withIntermediateDirectories: true
)
// Drop any stale/invalid file so the strict-perms create below succeeds.
try? fileManager.removeItem(at: url)

let fileDescriptor = open(url.path, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR)
guard fileDescriptor >= 0 else { return nil }
defer { _ = close(fileDescriptor) }

let wroteAllBytes = tokenData.withUnsafeBytes { rawBuffer in
guard let baseAddress = rawBuffer.baseAddress else { return false }
return write(fileDescriptor, baseAddress, rawBuffer.count) == rawBuffer.count
}
guard wroteAllBytes else {
try? fileManager.removeItem(at: url)
return nil
}
// Pin to 0600 in case umask widened the create mode.
try? fileManager.setAttributes([.posixPermissions: 0o600], ofItemAtPath: url.path)
return token
}
}

struct VSCodeServeWebLaunchOptions: Equatable {
let executableURL: URL
let arguments: [String]
let environment: [String: String]
}

/// Shapes the final process arguments + environment per launcher kind. The wrapper
/// and the cached code-server differ in supported flags and in how they manage the
/// secret keyring, so the two paths are handled explicitly here.
enum VSCodeServeWebLaunchOptionsBuilder {
static func launchOptions(
configuration: VSCodeCLILaunchConfiguration,
location: VSCodeServeWebRuntimeLocation,
port: Int
) -> VSCodeServeWebLaunchOptions {
var arguments = configuration.argumentsPrefix
arguments += [
"--accept-server-license-terms",
"--host", "127.0.0.1",
"--port", String(port),
"--connection-token-file", location.connectionTokenFileURL.path,
"--server-data-dir", location.serverDataDirectoryURL.path,
]
var environment = configuration.environment

switch configuration.launcherKind {
case .codeTunnelWrapper:
// `code-tunnel serve-web` does not accept --user-data-dir; it derives
// user data from --server-data-dir. Enable the CLI file keyring so VS
// Code Web auth/Settings Sync persist instead of using in-memory
// secret storage, and pin the CLI data dir for keyring stability.
environment["VSCODE_CLI_USE_FILE_KEYRING"] = "1"
let cliDataKey = VSCodeServeWebRuntimeLocator.cliDataDirectoryEnvironmentKey
let cliDataDirIsUnset = environment[cliDataKey]?
.trimmingCharacters(in: .whitespacesAndNewlines)
.isEmpty ?? true
if cliDataDirIsUnset {
environment[cliDataKey] = location.cliDataDirectoryURL.path
}
case .cachedCodeServer:
// The cached server binary accepts --user-data-dir directly.
arguments += ["--user-data-dir", location.userDataDirectoryURL.path]
}

return VSCodeServeWebLaunchOptions(
executableURL: configuration.executableURL,
arguments: arguments,
environment: environment
)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Large addition to already-oversized file

TerminalDirectoryOpenSupport.swift grew from 951 → 1241 lines (+290). The file already exceeded the 800-line guideline, and this PR adds four new independently-testable subsystems (token persistence, port resolution, location resolution, launch-options assembly) that have distinct responsibilities from the existing directory-detection and URL-building logic already in the file. The comprehensive test coverage introduced in cmuxTests/OmnibarAndToolsTests.swift is a signal that these subsystems are ready for their own source file or a small SwiftPM package boundary.

Rule Used: Flag Swift changes that add too much unrelated res... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — split the serve-web subsystem into its own Sources/App/VSCodeServeWebSupport.swift (wired into the Xcode project + normalized). TerminalDirectoryOpenSupport.swift drops from 1241 to 342 lines and now only holds the directory-open targets + workspace-shortcut mapping. (99d3aff)

— Claude Code

@blacksmith-sh

This comment has been minimized.

cmux and others added 2 commits June 26, 2026 02:24
Addresses the Aziz file-organization policy and review feedback: the new
serve-web persistence types (launch-config builder, runtime location
resolver, connection-token store, launch-options builder, controller, and
output collector) are a cohesive subsystem and were appended to the
already-large TerminalDirectoryOpenSupport.swift god file.

Move all VS Code serve-web types into Sources/App/VSCodeServeWebSupport.swift
(wired into the Xcode project + normalized), leaving directory-open targets
and workspace-shortcut mapping behind. No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The serve-web server-data, user-data, and CLI-data directories hold
long-lived VS Code Web auth, Settings Sync, and CLI keyring state. They
were created with default permissions, which under a wide umask could be
group/other-traversable. Create (and re-pin existing) directories at 0700
so they match the 0600 connection-token file. Addresses review feedback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/App/VSCodeServeWebSupport.swift`:
- Around line 496-506: The VSCodeServeWebController test seam is currently
exposed through a `#if` DEBUG-only makeForTesting helper, which should not live in
production source. Remove makeForTesting, widen the init(launchProcessOverride:)
visibility from private to internal, and let the test target construct
VSCodeServeWebController directly via `@testable` import using the existing
launchProcessOverride injection point.
- Around line 862-915: `ServeWebOutputCollector` is using blocking
synchronization (`NSLock`, `DispatchSemaphore`, and `wait`) in production code,
which should be replaced with Swift concurrency primitives. Refactor the
collector into an `actor` (or equivalent async state holder) so `append(_:)`,
`markProcessExited()`, and `webUIURL` access are thread-safe without locks, and
change `waitForURL(timeoutSeconds:)` into an async API that returns the URL or
emits it through `AsyncStream`/`CheckedContinuation`. Remove the semaphore
signaling path entirely and preserve the existing URL parsing behavior via
`VSCodeServeWebURLBuilder.extractWebUIURL(from:)`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7f7f9607-45bb-4ce7-b40e-86d7f8b75759

📥 Commits

Reviewing files that changed from the base of the PR and between 6d6c701 and e39b612.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (4)
  • Sources/App/TerminalDirectoryOpenSupport.swift
  • Sources/App/VSCodeServeWebSupport.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/OmnibarAndToolsTests.swift
💤 Files with no reviewable changes (1)
  • Sources/App/TerminalDirectoryOpenSupport.swift

Comment thread Sources/App/VSCodeServeWebSupport.swift Outdated
Comment thread Sources/App/VSCodeServeWebSupport.swift Outdated
Per cmux policy (no test/debug seams in production Sources/**), drop the
#if DEBUG makeForTesting factory on VSCodeServeWebController and widen its
init(launchProcessOverride:) from private to internal so the test target
constructs it directly via @testable import. Addresses CodeRabbit review.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@austinywang

Copy link
Copy Markdown
Contributor Author

Review feedback disposition

Addressed with code changes:

  • Directory permissions (Codex): serve-web server-data / user-data / cli-data dirs are now created (and re-pinned if pre-existing) at 0700, matching the 0600 connection-token file, since they hold long-lived auth/Settings Sync/keyring state. (e39b612)
  • Test/debug seam in production source (CodeRabbit): removed the #if DEBUG makeForTesting factory; VSCodeServeWebController.init(launchProcessOverride:) is now internal and the test constructs it directly via @testable import. (2f631ae)
  • Oversized file / file-size (Codex + Greptile): moved the entire serve-web subsystem out of the 1241-line TerminalDirectoryOpenSupport.swift into its own Sources/App/VSCodeServeWebSupport.swift. (99d3aff)

Consciously kept, with rationale (pre-existing / out-of-scope for a focused fix):

  • Blocking primitives in ServeWebOutputCollector (CodeRabbit): this NSLock/DispatchSemaphore output collector is pre-existing code relocated verbatim — swift-blocking-runtime.md explicitly allows "existing blocking code that the PR does not introduce or worsen." It drains a subprocess's stdout on a background queue with a bounded timeout. An actor/async migration of the whole generation-tracked Process lifecycle is a separate, larger refactor.
  • DispatchQueue synchronization / one-type-per-file: these cmux-policy-check findings are all on the pre-existing controller relocated into the new file; the move is for organization only and does not change behavior. Splitting one cohesive serve-web subsystem (controller + its pure builders/stores) into per-type files would fragment tightly-coupled collaborators.
  • Static-namespace builders (Greptile): the new types are pure, stateless transformations (or take FileManager via an injected default) and reach for no ambient globals — the controller injects ProcessInfo/Bundle/UserDefaults/FileManager. Kept as namespaces to match the established sibling builders in this domain.
  • Swift Testing (cmux-policy): new tests were added inside the existing all-XCTest OmnibarAndToolsTests.swift; converting only the new classes would split one behavior suite across two frameworks.

@blacksmith-sh

This comment has been minimized.

Previously, if the preferred stable port was already in use the launch fell
back to an ephemeral port (--port 0). That changes the server origin on
every relaunch, so users whose derived port happened to be occupied kept
losing VS Code Web auth/Settings Sync across launches — the exact bug this
fixes (#6595).

Now the launch tries the preferred port, then deterministic STABLE
alternates within the dynamic/private range, and persists whichever port
actually binds (so the origin does not drift back to a still-occupied
preferred port on a later launch). An ephemeral port is used only as a
final last resort when every stable candidate is occupied.

resolve() now returns just the location; the controller owns persistence
of the bound port. Adds candidateStablePorts() coverage (preferred-first,
deterministic, in-range, out-of-range-override handling) and updates the
port-resolution tests.

Addresses review feedback (autoreview).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lawrencecchen lawrencecchen added the stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening. label Sep 23, 2026
@github-project-automation github-project-automation Bot moved this from Todo to Done in cmux backlog Sep 23, 2026

This branch was successfully deployed

1 active deployment
Preview – cmux — da16c788 Deployed Jul 5, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Inline VS Code loses Settings Sync/auth state across reloads

3 participants