Skip to content

Commit d70a586

Browse files
authored
fix: specify --header-command when running coder start (#526)
1 parent 73f866d commit d70a586

File tree

5 files changed

+38
-24
lines changed

5 files changed

+38
-24
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
### Fixed
6+
7+
- Use `--header-command` properly when starting a workspace.
8+
59
## [v1.9.1](https://github.com/coder/vscode-coder/releases/tag/v1.9.1) 2025-05-27
610

711
### Fixed

src/api.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import * as vscode from "vscode"
99
import * as ws from "ws"
1010
import { errToStr } from "./api-helper"
1111
import { CertificateError } from "./error"
12+
import { getHeaderArgs } from "./headers"
1213
import { getProxyForUrl } from "./proxy"
1314
import { Storage } from "./storage"
1415
import { expandPath } from "./util"
@@ -168,6 +169,7 @@ export async function startWorkspaceIfStoppedOrFailed(
168169
const startArgs = [
169170
"--global-config",
170171
globalConfigDir,
172+
...getHeaderArgs(vscode.workspace.getConfiguration()),
171173
"start",
172174
"--yes",
173175
workspace.owner_name + "/" + workspace.name,

src/headers.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import * as cp from "child_process"
2+
import * as os from "os"
23
import * as util from "util"
3-
4-
import { WorkspaceConfiguration } from "vscode"
4+
import type { WorkspaceConfiguration } from "vscode"
5+
import { escapeCommandArg } from "./util"
56

67
export interface Logger {
78
writeToCoderOutputChannel(message: string): void
@@ -25,6 +26,23 @@ export function getHeaderCommand(config: WorkspaceConfiguration): string | undef
2526
return cmd
2627
}
2728

29+
export function getHeaderArgs(config: WorkspaceConfiguration): string[] {
30+
// Escape a command line to be executed by the Coder binary, so ssh doesn't substitute variables.
31+
const escapeSubcommand: (str: string) => string =
32+
os.platform() === "win32"
33+
? // On Windows variables are %VAR%, and we need to use double quotes.
34+
(str) => escapeCommandArg(str).replace(/%/g, "%%")
35+
: // On *nix we can use single quotes to escape $VARS.
36+
// Note single quotes cannot be escaped inside single quotes.
37+
(str) => `'${str.replace(/'/g, "'\\''")}'`
38+
39+
const command = getHeaderCommand(config)
40+
if (!command) {
41+
return []
42+
}
43+
return ["--header-command", escapeSubcommand(command)]
44+
}
45+
2846
// TODO: getHeaders might make more sense to directly implement on Storage
2947
// but it is difficult to test Storage right now since we use vitest instead of
3048
// the standard extension testing framework which would give us access to vscode

src/remote.ts

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ import { extractAgents } from "./api-helper"
1414
import * as cli from "./cliManager"
1515
import { Commands } from "./commands"
1616
import { featureSetForVersion, FeatureSet } from "./featureSet"
17-
import { getHeaderCommand } from "./headers"
17+
import { getHeaderArgs } from "./headers"
1818
import { Inbox } from "./inbox"
1919
import { SSHConfig, SSHValues, mergeSSHConfigValues } from "./sshConfig"
2020
import { computeSSHProperties, sshSupportsSetEnv } from "./sshSupport"
2121
import { Storage } from "./storage"
22-
import { AuthorityPrefix, expandPath, findPort, parseRemoteAuthority } from "./util"
22+
import { AuthorityPrefix, escapeCommandArg, expandPath, findPort, parseRemoteAuthority } from "./util"
2323
import { WorkspaceMonitor } from "./workspaceMonitor"
2424

2525
export interface RemoteDetails extends vscode.Disposable {
@@ -611,32 +611,18 @@ export class Remote {
611611
const sshConfig = new SSHConfig(sshConfigFile)
612612
await sshConfig.load()
613613

614-
const escape = (str: string): string => `"${str.replace(/"/g, '\\"')}"`
615-
// Escape a command line to be executed by the Coder binary, so ssh doesn't substitute variables.
616-
const escapeSubcommand: (str: string) => string =
617-
os.platform() === "win32"
618-
? // On Windows variables are %VAR%, and we need to use double quotes.
619-
(str) => escape(str).replace(/%/g, "%%")
620-
: // On *nix we can use single quotes to escape $VARS.
621-
// Note single quotes cannot be escaped inside single quotes.
622-
(str) => `'${str.replace(/'/g, "'\\''")}'`
623-
624-
// Add headers from the header command.
625-
let headerArg = ""
626-
const headerCommand = getHeaderCommand(vscode.workspace.getConfiguration())
627-
if (typeof headerCommand === "string" && headerCommand.trim().length > 0) {
628-
headerArg = ` --header-command ${escapeSubcommand(headerCommand)}`
629-
}
614+
const headerArgs = getHeaderArgs(vscode.workspace.getConfiguration())
615+
const headerArgList = headerArgs.length > 0 ? ` ${headerArgs.join(" ")}` : ""
630616

631617
const hostPrefix = label ? `${AuthorityPrefix}.${label}--` : `${AuthorityPrefix}--`
632618

633619
const proxyCommand = featureSet.wildcardSSH
634-
? `${escape(binaryPath)}${headerArg} --global-config ${escape(
620+
? `${escapeCommandArg(binaryPath)}${headerArgList} --global-config ${escapeCommandArg(
635621
path.dirname(this.storage.getSessionTokenPath(label)),
636-
)} ssh --stdio --usage-app=vscode --disable-autostart --network-info-dir ${escape(this.storage.getNetworkInfoPath())}${await this.formatLogArg(logDir)} --ssh-host-prefix ${hostPrefix} %h`
637-
: `${escape(binaryPath)}${headerArg} vscodessh --network-info-dir ${escape(
622+
)} ssh --stdio --usage-app=vscode --disable-autostart --network-info-dir ${escapeCommandArg(this.storage.getNetworkInfoPath())}${await this.formatLogArg(logDir)} --ssh-host-prefix ${hostPrefix} %h`
623+
: `${escapeCommandArg(binaryPath)}${headerArgList} vscodessh --network-info-dir ${escapeCommandArg(
638624
this.storage.getNetworkInfoPath(),
639-
)}${await this.formatLogArg(logDir)} --session-token-file ${escape(this.storage.getSessionTokenPath(label))} --url-file ${escape(
625+
)}${await this.formatLogArg(logDir)} --session-token-file ${escapeCommandArg(this.storage.getSessionTokenPath(label))} --url-file ${escapeCommandArg(
640626
this.storage.getUrlPath(label),
641627
)} %h`
642628

src/util.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,3 +136,7 @@ export function countSubstring(needle: string, haystack: string): number {
136136
}
137137
return count
138138
}
139+
140+
export function escapeCommandArg(arg: string): string {
141+
return `"${arg.replace(/"/g, '\\"')}"`
142+
}

0 commit comments

Comments
 (0)