Skip to content

Commit 43ba121

Browse files
authored
Add support for CLI global flag configurations and update all CLI invocations (#577)
#500
1 parent a70f4d9 commit 43ba121

File tree

7 files changed

+136
-20
lines changed

7 files changed

+136
-20
lines changed

CHANGELOG.md

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

77
- Always enable verbose (`-v`) flag when a log directory is configured (`coder.proxyLogDir`).
88

9+
### Added
10+
11+
- Add support for CLI global flag configurations through the `coder.globalFlags` setting.
12+
913
## [1.10.1](https://github.com/coder/vscode-coder/releases/tag/v1.10.1) 2025-08-13
1014

1115
### Fixed

package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,13 @@
119119
"markdownDescription": "Disable Coder CLI signature verification, which can be useful if you run an unsigned fork of the binary.",
120120
"type": "boolean",
121121
"default": false
122+
},
123+
"coder.globalFlags": {
124+
"markdownDescription": "Global flags to pass to every Coder CLI invocation. Enter each flag as a separate array item; values are passed verbatim and in order. Do **not** include the `coder` command itself. See the [CLI reference](https://coder.com/docs/reference/cli) for available global flags.\n\nNote that for `--header-command`, precedence is: `#coder.headerCommand#` setting, then `CODER_HEADER_COMMAND` environment variable, then the value specified here. The `--global-config` flag is explicitly ignored.",
125+
"type": "array",
126+
"items": {
127+
"type": "string"
128+
}
122129
}
123130
}
124131
},

src/api.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import * as ws from "ws";
1313
import { errToStr } from "./api-helper";
1414
import { CertificateError } from "./error";
1515
import { FeatureSet } from "./featureSet";
16-
import { getHeaderArgs } from "./headers";
16+
import { getGlobalFlags } from "./globalFlags";
1717
import { getProxyForUrl } from "./proxy";
1818
import { Storage } from "./storage";
1919
import { expandPath } from "./util";
@@ -186,9 +186,7 @@ export async function startWorkspaceIfStoppedOrFailed(
186186

187187
return new Promise((resolve, reject) => {
188188
const startArgs = [
189-
"--global-config",
190-
globalConfigDir,
191-
...getHeaderArgs(vscode.workspace.getConfiguration()),
189+
...getGlobalFlags(vscode.workspace.getConfiguration(), globalConfigDir),
192190
"start",
193191
"--yes",
194192
workspace.owner_name + "/" + workspace.name,
@@ -197,7 +195,7 @@ export async function startWorkspaceIfStoppedOrFailed(
197195
startArgs.push(...["--reason", "vscode_connection"]);
198196
}
199197

200-
const startProcess = spawn(binPath, startArgs);
198+
const startProcess = spawn(binPath, startArgs, { shell: true });
201199

202200
startProcess.stdout.on("data", (data: Buffer) => {
203201
data

src/commands.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ import * as vscode from "vscode";
1010
import { makeCoderSdk, needToken } from "./api";
1111
import { extractAgents } from "./api-helper";
1212
import { CertificateError } from "./error";
13+
import { getGlobalFlags } from "./globalFlags";
1314
import { Storage } from "./storage";
14-
import { toRemoteAuthority, toSafeHost } from "./util";
15+
import { escapeCommandArg, toRemoteAuthority, toSafeHost } from "./util";
1516
import {
1617
AgentTreeItem,
1718
WorkspaceTreeItem,
@@ -503,12 +504,16 @@ export class Commands {
503504
this.restClient,
504505
toSafeHost(url),
505506
);
506-
const escape = (str: string): string =>
507-
`"${str.replace(/"/g, '\\"')}"`;
507+
508+
const configDir = path.dirname(
509+
this.storage.getSessionTokenPath(toSafeHost(url)),
510+
);
511+
const globalFlags = getGlobalFlags(
512+
vscode.workspace.getConfiguration(),
513+
configDir,
514+
);
508515
terminal.sendText(
509-
`${escape(binary)} ssh --global-config ${escape(
510-
path.dirname(this.storage.getSessionTokenPath(toSafeHost(url))),
511-
)} ${app.workspace_name}`,
516+
`${escapeCommandArg(binary)}${` ${globalFlags.join(" ")}`} ssh ${app.workspace_name}`,
512517
);
513518
await new Promise((resolve) => setTimeout(resolve, 5000));
514519
terminal.sendText(app.command ?? "");

src/globalFlags.test.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { it, expect, describe } from "vitest";
2+
import { WorkspaceConfiguration } from "vscode";
3+
import { getGlobalFlags } from "./globalFlags";
4+
5+
describe("Global flags suite", () => {
6+
it("should return global-config and header args when no global flags configured", () => {
7+
const config = {
8+
get: () => undefined,
9+
} as unknown as WorkspaceConfiguration;
10+
11+
expect(getGlobalFlags(config, "/config/dir")).toStrictEqual([
12+
"--global-config",
13+
'"/config/dir"',
14+
]);
15+
});
16+
17+
it("should return global flags from config with global-config appended", () => {
18+
const config = {
19+
get: (key: string) =>
20+
key === "coder.globalFlags"
21+
? ["--verbose", "--disable-direct-connections"]
22+
: undefined,
23+
} as unknown as WorkspaceConfiguration;
24+
25+
expect(getGlobalFlags(config, "/config/dir")).toStrictEqual([
26+
"--verbose",
27+
"--disable-direct-connections",
28+
"--global-config",
29+
'"/config/dir"',
30+
]);
31+
});
32+
33+
it("should not filter duplicate global-config flags, last takes precedence", () => {
34+
const config = {
35+
get: (key: string) =>
36+
key === "coder.globalFlags"
37+
? [
38+
"-v",
39+
"--global-config /path/to/ignored",
40+
"--disable-direct-connections",
41+
]
42+
: undefined,
43+
} as unknown as WorkspaceConfiguration;
44+
45+
expect(getGlobalFlags(config, "/config/dir")).toStrictEqual([
46+
"-v",
47+
"--global-config /path/to/ignored",
48+
"--disable-direct-connections",
49+
"--global-config",
50+
'"/config/dir"',
51+
]);
52+
});
53+
54+
it("should not filter header-command flags, header args appended at end", () => {
55+
const config = {
56+
get: (key: string) => {
57+
if (key === "coder.headerCommand") {
58+
return "echo test";
59+
}
60+
if (key === "coder.globalFlags") {
61+
return ["-v", "--header-command custom", "--no-feature-warning"];
62+
}
63+
return undefined;
64+
},
65+
} as unknown as WorkspaceConfiguration;
66+
67+
const result = getGlobalFlags(config, "/config/dir");
68+
expect(result).toStrictEqual([
69+
"-v",
70+
"--header-command custom", // ignored by CLI
71+
"--no-feature-warning",
72+
"--global-config",
73+
'"/config/dir"',
74+
"--header-command",
75+
"'echo test'",
76+
]);
77+
});
78+
});

src/globalFlags.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { WorkspaceConfiguration } from "vscode";
2+
import { getHeaderArgs } from "./headers";
3+
import { escapeCommandArg } from "./util";
4+
5+
/**
6+
* Returns global configuration flags for Coder CLI commands.
7+
* Always includes the `--global-config` argument with the specified config directory.
8+
*/
9+
export function getGlobalFlags(
10+
configs: WorkspaceConfiguration,
11+
configDir: string,
12+
): string[] {
13+
// Last takes precedence/overrides previous ones
14+
return [
15+
...(configs.get<string[]>("coder.globalFlags") || []),
16+
...["--global-config", escapeCommandArg(configDir)],
17+
...getHeaderArgs(configs),
18+
];
19+
}

src/remote.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import { extractAgents } from "./api-helper";
2626
import * as cli from "./cliManager";
2727
import { Commands } from "./commands";
2828
import { featureSetForVersion, FeatureSet } from "./featureSet";
29-
import { getHeaderArgs } from "./headers";
29+
import { getGlobalFlags } from "./globalFlags";
3030
import { Inbox } from "./inbox";
3131
import { SSHConfig, SSHValues, mergeSSHConfigValues } from "./sshConfig";
3232
import { computeSSHProperties, sshSupportsSetEnv } from "./sshSupport";
@@ -758,19 +758,15 @@ export class Remote {
758758
const sshConfig = new SSHConfig(sshConfigFile);
759759
await sshConfig.load();
760760

761-
const headerArgs = getHeaderArgs(vscode.workspace.getConfiguration());
762-
const headerArgList =
763-
headerArgs.length > 0 ? ` ${headerArgs.join(" ")}` : "";
764-
765761
const hostPrefix = label
766762
? `${AuthorityPrefix}.${label}--`
767763
: `${AuthorityPrefix}--`;
768764

765+
const globalConfigs = this.globalConfigs(label);
766+
769767
const proxyCommand = featureSet.wildcardSSH
770-
? `${escapeCommandArg(binaryPath)}${headerArgList} --global-config ${escapeCommandArg(
771-
path.dirname(this.storage.getSessionTokenPath(label)),
772-
)} ssh --stdio --usage-app=vscode --disable-autostart --network-info-dir ${escapeCommandArg(this.storage.getNetworkInfoPath())}${await this.formatLogArg(logDir)} --ssh-host-prefix ${hostPrefix} %h`
773-
: `${escapeCommandArg(binaryPath)}${headerArgList} vscodessh --network-info-dir ${escapeCommandArg(
768+
? `${escapeCommandArg(binaryPath)}${globalConfigs} ssh --stdio --usage-app=vscode --disable-autostart --network-info-dir ${escapeCommandArg(this.storage.getNetworkInfoPath())}${await this.formatLogArg(logDir)} --ssh-host-prefix ${hostPrefix} %h`
769+
: `${escapeCommandArg(binaryPath)}${globalConfigs} vscodessh --network-info-dir ${escapeCommandArg(
774770
this.storage.getNetworkInfoPath(),
775771
)}${await this.formatLogArg(logDir)} --session-token-file ${escapeCommandArg(this.storage.getSessionTokenPath(label))} --url-file ${escapeCommandArg(
776772
this.storage.getUrlPath(label),
@@ -828,6 +824,15 @@ export class Remote {
828824
return sshConfig.getRaw();
829825
}
830826

827+
private globalConfigs(label: string): string {
828+
const vscodeConfig = vscode.workspace.getConfiguration();
829+
const args = getGlobalFlags(
830+
vscodeConfig,
831+
path.dirname(this.storage.getSessionTokenPath(label)),
832+
);
833+
return ` ${args.join(" ")}`;
834+
}
835+
831836
// showNetworkUpdates finds the SSH process ID that is being used by this
832837
// workspace and reads the file being created by the Coder CLI.
833838
private showNetworkUpdates(sshPid: number): vscode.Disposable {

0 commit comments

Comments
 (0)