Describe the bug
Environment:
• GitHub Copilot CLI version: 1.0.74
• OS: macOS (Darwin)
• Config: ~/.copilot/mcp-config.json , stdio MCP server launched via sh -c "..."
Summary:
When an MCP server's args array contains a shell command string with nested bash parameter-expansion defaults ( ${A:-${B:-$(cmd)}} ), and the outer variable ( A ) is already present in Copilot CLI's own process environment, Copilot CLI appears to perform its own variable substitution pass over the args string before handing it to the shell. This pass does not correctly track nested { / } pairs — it matches up to the first } it finds, substitutes the outer variable's current env value there, and leaves the remainder of the original nested expression (including its own trailing } ) behind as a literal string suffix. This silently corrupts the resulting value (e.g. an auth token gains a stray trailing } ), producing hard-to-diagnose authentication failures with no error at MCP startup.
Repro config ( mcp-config.json entry):
{
"jira": {
"command": "sh",
"args": [
"-c",
"export JIRA_ACCESS_TOKEN="${JIRA_ACCESS_TOKEN:-${JIRA_PAT:-$(keychain.sh get JIRA_PAT)}}" JIRA_BASE_URL="https://example.atlassian.net"; exec node jira-mcp-server.mjs"
]
}
}
Affected version
GitHub Copilot CLI 1.0.74.
Steps to reproduce the behavior
Repro steps: ┃
┃
- Ensure JIRA_ACCESS_TOKEN is already exported in the shell/parent environment that launches Copilot CLI (e.g. via a startup script), with some valid value TOKEN123 . ┃
- Configure an MCP server whose args includes the nested-default pattern above. ┃
- Start Copilot CLI, and let it spawn the MCP server. ┃
- Inspect the spawned child process's actual environment (e.g. ps eww -p on macOS, or /proc//environ on Linux). ┃
┃
Expected: JIRA_ACCESS_TOKEN in the child process env equals TOKEN123 (the shell resolves ${JIRA_ACCESS_TOKEN:-...} itself, since JIRA_ACCESS_TOKEN is already set — no substitution ┃
by Copilot CLI should be needed or should not corrupt the string if performed). ┃
┃
Actual: JIRA_ACCESS_TOKEN in the child process env is TOKEN123} — a stray trailing } character is appended, corrupting the value. This causes the MCP server's outbound API calls to ┃
fail authentication (e.g. Jira returns "Field 'assignee' does not exist or cannot be viewed by anonymous users" because the malformed Bearer token is rejected and the request is treated ┃
as unauthenticated). ┃
┃
Impact: Any MCP server config relying on the common bash idiom of nested ${VAR:-default} fallback chains (a standard pattern for "env var, else secret store, else command substitution") ┃
will have secrets/tokens silently corrupted whenever the outer variable happens to already be set in Copilot CLI's environment — with no startup error, only a downstream, misleading auth ┃
failure from the third-party API. This is especially dangerous because: ┃
┃
• The corruption is silent (no warning/error from Copilot CLI itself). ┃
• The same MCP config file works correctly in other tools/clients (e.g. Amp) that don't pre-template args , making it look like a server-side/token/auth issue rather than a CLI ┃
templating bug. ┃
┃
Suggested fix: Either (a) don't perform Copilot CLI's own variable substitution on MCP server args strings that are passed to a command /shell launcher — let the shell handle its own ┃
expansion — or (b) if substitution is intentional (e.g. to support ${HOME} ), use a proper brace-depth-aware parser (or a stricter regex like ${(\w+)} with no :- support) rather ┃
than matching to the first } .
Expected behavior
No response
Additional context
No response
Describe the bug
Environment:
• GitHub Copilot CLI version: 1.0.74
• OS: macOS (Darwin)
• Config: ~/.copilot/mcp-config.json , stdio MCP server launched via sh -c "..."
Summary:
When an MCP server's args array contains a shell command string with nested bash parameter-expansion defaults ( ${A:-${B:-$(cmd)}} ), and the outer variable ( A ) is already present in Copilot CLI's own process environment, Copilot CLI appears to perform its own variable substitution pass over the args string before handing it to the shell. This pass does not correctly track nested { / } pairs — it matches up to the first } it finds, substitutes the outer variable's current env value there, and leaves the remainder of the original nested expression (including its own trailing } ) behind as a literal string suffix. This silently corrupts the resulting value (e.g. an auth token gains a stray trailing } ), producing hard-to-diagnose authentication failures with no error at MCP startup.
Repro config ( mcp-config.json entry):
{
"jira": {
"command": "sh",
"args": [
"-c",
"export JIRA_ACCESS_TOKEN="${JIRA_ACCESS_TOKEN:-${JIRA_PAT:-$(keychain.sh get JIRA_PAT)}}" JIRA_BASE_URL="https://example.atlassian.net"; exec node jira-mcp-server.mjs"
]
}
}
Affected version
GitHub Copilot CLI 1.0.74.
Steps to reproduce the behavior
Repro steps: ┃
┃
┃
Expected: JIRA_ACCESS_TOKEN in the child process env equals TOKEN123 (the shell resolves ${JIRA_ACCESS_TOKEN:-...} itself, since JIRA_ACCESS_TOKEN is already set — no substitution ┃
by Copilot CLI should be needed or should not corrupt the string if performed). ┃
┃
Actual: JIRA_ACCESS_TOKEN in the child process env is TOKEN123} — a stray trailing } character is appended, corrupting the value. This causes the MCP server's outbound API calls to ┃
fail authentication (e.g. Jira returns "Field 'assignee' does not exist or cannot be viewed by anonymous users" because the malformed Bearer token is rejected and the request is treated ┃
as unauthenticated). ┃
┃
Impact: Any MCP server config relying on the common bash idiom of nested ${VAR:-default} fallback chains (a standard pattern for "env var, else secret store, else command substitution") ┃
will have secrets/tokens silently corrupted whenever the outer variable happens to already be set in Copilot CLI's environment — with no startup error, only a downstream, misleading auth ┃
failure from the third-party API. This is especially dangerous because: ┃
┃
• The corruption is silent (no warning/error from Copilot CLI itself). ┃
• The same MCP config file works correctly in other tools/clients (e.g. Amp) that don't pre-template args , making it look like a server-side/token/auth issue rather than a CLI ┃
templating bug. ┃
┃
Suggested fix: Either (a) don't perform Copilot CLI's own variable substitution on MCP server args strings that are passed to a command /shell launcher — let the shell handle its own ┃
expansion — or (b) if substitution is intentional (e.g. to support ${HOME} ), use a proper brace-depth-aware parser (or a stricter regex like ${(\w+)} with no :- support) rather ┃
than matching to the first } .
Expected behavior
No response
Additional context
No response