Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,25 @@ Code v99.99.999

## Unreleased

### Fixed

- `--idle-timeout-seconds` was only validated when passed as `--idle-timeout-seconds=<value>`;
values of 60 or less passed as `--idle-timeout-seconds <value>` were silently accepted.

## [4.138.0](https://github.com/coder/code-server/releases/tag/v4.138.0) - 2026-09-19

Code v1.138.0

### Changed

- Update to Code 1.138.0

### Fixed

- Preserve the original path, query parameters, and fragment when rewriting
localhost URLs through the port proxy. If `VSCODE_PROXY_URI` has any query
parameters or fragments, those will be lost.

## [4.137.1](https://github.com/MotusLabs/code-server/releases/tag/v4.137.1) - 2026-09-18

### Changed
Expand Down
4 changes: 2 additions & 2 deletions ci/helm-chart/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 3.52.1
version: 3.53.0

# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
appVersion: 4.137.1
appVersion: 4.138.0
2 changes: 1 addition & 1 deletion ci/helm-chart/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ replicaCount: 1

image:
repository: ghcr.io/motuslabs/code-server
tag: '4.137.1'
tag: '4.138.0'
pullPolicy: Always

# Specifies one or more secrets to be used when pulling images from a
Expand Down
2 changes: 1 addition & 1 deletion lib/vscode
Submodule vscode updated 2114 files
4 changes: 2 additions & 2 deletions patches/external-file-actions.diff
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/contextkeys.ts
@@ -6,10 +6,10 @@
import { Disposable } from '../../base/common/lifecycle.js';
import { IContextKeyService, IContextKey, setConstant as setConstantContextKey } from '../../platform/contextkey/common/contextkey.js';
import { IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext, ProductQualityContext, IsMobileContext } from '../../platform/contextkey/common/contextkeys.js';
import { IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext, ProductQualityContext, IsMobileContext, IsChromeOSContext } from '../../platform/contextkey/common/contextkeys.js';
-import { SplitEditorsVertically, InEditorZenModeContext, AuxiliaryBarVisibleContext, SecondarySideBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsMainEditorCenteredLayoutContext, MainEditorAreaVisibleContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsMainWindowFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, TitleBarVisibleContext, TitleBarStyleContext, IsAuxiliaryWindowFocusedContext, ActiveEditorGroupEmptyContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorGroupLockedContext, MultipleEditorGroupsContext, EditorsVisibleContext, AuxiliaryBarMaximizedContext, InAutomationContext, IsSessionsWindowContext } from '../common/contextkeys.js';
+import { IsEnabledFileDownloads, IsEnabledFileUploads, SplitEditorsVertically, InEditorZenModeContext, AuxiliaryBarVisibleContext, SecondarySideBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsMainEditorCenteredLayoutContext, MainEditorAreaVisibleContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsMainWindowFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, TitleBarVisibleContext, TitleBarStyleContext, IsAuxiliaryWindowFocusedContext, ActiveEditorGroupEmptyContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorGroupLockedContext, MultipleEditorGroupsContext, EditorsVisibleContext, AuxiliaryBarMaximizedContext, InAutomationContext, IsSessionsWindowContext } from '../common/contextkeys.js';
import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from '../services/editor/common/editorGroupsService.js';
Expand All @@ -147,7 +147,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/contextkeys.ts
@IProductService private readonly productService: IProductService,
@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService,
@IEditorService private readonly editorService: IEditorService,
@@ -205,6 +205,10 @@ export class WorkbenchContextKeysHandler
@@ -206,6 +206,10 @@ export class WorkbenchContextKeysHandler
this.auxiliaryBarMaximizedContext = AuxiliaryBarMaximizedContext.bindTo(this.contextKeyService);
this.auxiliaryBarMaximizedContext.set(this.layoutService.isAuxiliaryBarMaximized());

Expand Down
4 changes: 2 additions & 2 deletions patches/getting-started.diff
Original file line number Diff line number Diff line change
Expand Up @@ -216,13 +216,13 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/contextkeys.ts
@@ -6,7 +6,7 @@
import { Disposable } from '../../base/common/lifecycle.js';
import { IContextKeyService, IContextKey, setConstant as setConstantContextKey } from '../../platform/contextkey/common/contextkey.js';
import { IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext, ProductQualityContext, IsMobileContext } from '../../platform/contextkey/common/contextkeys.js';
import { IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext, ProductQualityContext, IsMobileContext, IsChromeOSContext } from '../../platform/contextkey/common/contextkeys.js';
-import { IsEnabledFileDownloads, IsEnabledFileUploads, SplitEditorsVertically, InEditorZenModeContext, AuxiliaryBarVisibleContext, SecondarySideBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsMainEditorCenteredLayoutContext, MainEditorAreaVisibleContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsMainWindowFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, TitleBarVisibleContext, TitleBarStyleContext, IsAuxiliaryWindowFocusedContext, ActiveEditorGroupEmptyContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorGroupLockedContext, MultipleEditorGroupsContext, EditorsVisibleContext, AuxiliaryBarMaximizedContext, InAutomationContext, IsSessionsWindowContext } from '../common/contextkeys.js';
+import { IsEnabledFileDownloads, IsEnabledFileUploads, IsEnabledCoderGettingStarted, SplitEditorsVertically, InEditorZenModeContext, AuxiliaryBarVisibleContext, SecondarySideBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsMainEditorCenteredLayoutContext, MainEditorAreaVisibleContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsMainWindowFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, TitleBarVisibleContext, TitleBarStyleContext, IsAuxiliaryWindowFocusedContext, ActiveEditorGroupEmptyContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorGroupLockedContext, MultipleEditorGroupsContext, EditorsVisibleContext, AuxiliaryBarMaximizedContext, InAutomationContext, IsSessionsWindowContext } from '../common/contextkeys.js';
import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from '../services/editor/common/editorGroupsService.js';
import { IConfigurationService } from '../../platform/configuration/common/configuration.js';
import { IBrowserWorkbenchEnvironmentService } from '../services/environment/browser/environmentService.js';
@@ -208,6 +208,7 @@ export class WorkbenchContextKeysHandler
@@ -209,6 +209,7 @@ export class WorkbenchContextKeysHandler
// code-server
IsEnabledFileDownloads.bindTo(this.contextKeyService).set(this.environmentService.isEnabledFileDownloads ?? true)
IsEnabledFileUploads.bindTo(this.contextKeyService).set(this.environmentService.isEnabledFileUploads ?? true)
Expand Down
2 changes: 1 addition & 1 deletion patches/proposed-api.diff
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Index: code-server/lib/vscode/src/vs/workbench/services/extensions/common/extens
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/services/extensions/common/extensions.ts
+++ code-server/lib/vscode/src/vs/workbench/services/extensions/common/extensions.ts
@@ -324,6 +324,10 @@ function extensionDescriptionArrayToMap(
@@ -321,6 +321,10 @@ function extensionDescriptionArrayToMap(
}

export function isProposedApiEnabled(extension: IExtensionDescription, proposal: ApiProposalName): boolean {
Expand Down
19 changes: 15 additions & 4 deletions patches/proxy-uri.diff
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,18 @@ This has e2e tests.
For the `asExternalUri` changes, you'll need to test manually by:
1. running code-server with the test extension
2. Command Palette > code-server: asExternalUri test
3. input a url like http://localhost:3000
4. it should show a notification and show output as <code-server>/proxy/3000
3. input a url like http://localhost:3000/my/path?token=abc#section
4. it should show a notification and show output as
<code-server>/proxy/3000/my/path?token%3Dabc#section

Do the same thing but set `VSCODE_PROXY_URI: "https://{{port}}-main-workspace-name-user-name.coder.com"`
and the output should replace `{{port}}` with port used in input url.

The rewritten URI must preserve the original path, query, and fragment (see
#7668). Append the original path to the proxy path using URI components so
encoded characters are not decoded or encoded twice. This also works with
proxy templates that do not have a trailing slash.

This also enables the forwared ports view panel by default.

Lastly, it adds a tunnelProvider so that ports are forwarded using code-server's
Expand Down Expand Up @@ -104,7 +110,7 @@ Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench.ts
import type { IURLCallbackProvider } from '../../../workbench/services/url/browser/urlService.js';
import { create } from '../../../workbench/workbench.web.main.internal.js';

@@ -612,6 +613,39 @@ class WorkspaceProvider implements IWork
@@ -612,6 +613,44 @@ class WorkspaceProvider implements IWork
settingsSyncOptions: config.settingsSyncOptions ? { enabled: config.settingsSyncOptions.enabled, } : undefined,
workspaceProvider: WorkspaceProvider.create(config),
urlCallbackProvider: new LocalStorageURLCallbackProvider(config.callbackRoute),
Expand All @@ -116,7 +122,12 @@ Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench.ts
+ const renderedTemplate = config.productConfiguration.proxyEndpointTemplate
+ .replace('{{port}}', localhostMatch.port.toString())
+ .replace('{{host}}', window.location.host)
+ resolvedUri = URI.parse(new URL(renderedTemplate, window.location.href).toString())
+ const proxyUri = URI.parse(new URL(renderedTemplate, window.location.href).toString())
+ resolvedUri = proxyUri.with({
+ path: proxyUri.path.replace(/\/$/, '') + uri.path,
+ query: uri.query,
+ fragment: uri.fragment,
+ })
+ } else {
+ throw new Error(`Failed to resolve external URI: ${uri.toString()}. Could not determine base url because productConfiguration missing.`)
+ }
Expand Down
8 changes: 4 additions & 4 deletions src/node/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -429,10 +429,6 @@ export const parse = (
throw new Error("--github-auth can only be set in the config file or passed in via $GITHUB_TOKEN")
}

if (key === "idle-timeout-seconds" && Number(value) <= 60) {
throw new Error("--idle-timeout-seconds must be greater than 60 seconds.")
}

const option = options[key]
if (option.type === "boolean") {
;(args[key] as boolean) = true
Expand All @@ -452,6 +448,10 @@ export const parse = (
throw error(`--${key} requires a value`)
}

if (key === "idle-timeout-seconds" && Number(value) <= 60) {
throw new Error("--idle-timeout-seconds must be greater than 60 seconds.")
}

if (option.type === OptionalString && value === "false") {
continue
}
Expand Down
61 changes: 61 additions & 0 deletions test/e2e/extensions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,56 @@ function runTestExtensionTests() {
const normalizedAddress = address.replace(/\/+$/, "")
await expect(codeServerPage.page.getByText(`Info: proxyUri: ${normalizedAddress}/proxy/{{port}}/`)).toBeVisible()
})

runExternalUriTests()
}

function runExternalUriTests(proxyEndpointTemplate?: string) {
const cases = [
{
name: "path",
input: "http://127.0.0.1:1234/my/path",
suffix: "/my/path",
},
{
name: "query and fragment",
input: "http://localhost:1234/my/path?token=abc&mode=preview#section",
suffix: "/my/path?token%3Dabc%26mode%3Dpreview#section",
},
{
name: "encoded components",
input: "http://0.0.0.0:1234/my%20path/%23file?token=a%20b#my%20section",
suffix: "/my%20path/%23file?token%3Da%20b#my%20section",
},
{
name: "root path",
input: "http://127.0.0.1:1234/",
suffix: "/",
},
]

for (const { name, input, suffix } of cases) {
test(`asExternalUri should preserve ${name}`, async ({ codeServerPage }) => {
const address = await getMaybeProxiedCodeServer(codeServerPage)
const normalizedAddress = address.replace(/\/+$/, "")
const proxyBase = proxyEndpointTemplate
? new URL(proxyEndpointTemplate.replace("{{port}}", "1234"), `${normalizedAddress}/`).toString()
: `${normalizedAddress}/proxy/1234/`

await codeServerPage.waitForTestExtensionLoaded()
await codeServerPage.executeCommandViaMenus("code-server: asExternalUri test")

const inputBox = codeServerPage.page.locator(".quick-input-widget input")
await inputBox.fill(input)
await inputBox.press("Enter")

// The test extension displays URI.toString(), which also encodes query delimiters.
const output = `${proxyBase.replace(/\/$/, "")}${suffix}`
await expect(
codeServerPage.page.getByText(`Info: input: ${input} output: ${output}`, { exact: true }),
).toBeVisible()
})
}
}

const flags = ["--disable-workspace-trust", "--extensions-dir", path.join(__dirname, "./extensions")]
Expand All @@ -23,6 +73,17 @@ describe("Extensions", flags, {}, () => {
runTestExtensionTests()
})

for (const proxyEndpointTemplate of ["./proxy/{{port}}", "https://{{port}}-workspace.example.com"]) {
describe(
`Extensions with VSCODE_PROXY_URI=${proxyEndpointTemplate}`,
flags,
{ VSCODE_PROXY_URI: proxyEndpointTemplate },
() => {
runExternalUriTests(proxyEndpointTemplate)
},
)
}

if (process.env.USE_PROXY !== "1") {
describe("Extensions with --cert", [...flags, "--cert"], {}, () => {
runTestExtensionTests()
Expand Down
10 changes: 10 additions & 0 deletions test/unit/node/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,16 @@ describe("parser", () => {
expect(() => parse(["--log", "invalid"])).toThrowError(/--log valid values: \[trace, debug, info, warn, error\]/)
})

it("should error if idle-timeout-seconds is too low", () => {
expect(() => parse(["--idle-timeout-seconds=60"])).toThrowError(
/--idle-timeout-seconds must be greater than 60 seconds/,
)
expect(() => parse(["--idle-timeout-seconds", "60"])).toThrowError(
/--idle-timeout-seconds must be greater than 60 seconds/,
)
expect(parse(["--idle-timeout-seconds", "61"])).toEqual({ "idle-timeout-seconds": 61 })
})

it("should error if the option doesn't exist", () => {
expect(() => parse(["--foo"])).toThrowError(/Unknown option --foo/)
})
Expand Down
Loading