Skip to content

Commit 0a732c3

Browse files
kfranceclaude
authored andcommitted
fix: environment variables now correctly take precedence over config file values
The README documented that environment variables take precedence over config file values, but the code had the opposite behavior. This fix corrects the precedence order in getRawOption to match the documentation: 1. CLI flags (highest priority) 2. Environment variables 3. Config file (lowest priority) Also adds subprocess-based tests that verify: - Env vars win when both env var and config file are present - Config file is used as fallback when no env var is set 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 80dc1be commit 0a732c3

3 files changed

Lines changed: 109 additions & 2 deletions

File tree

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+
- environment variables now correctly take precedence over config file values
8+
59
## [1.6.0] - 2026-01-05
610

711
### Added

src/config.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,9 @@ export type Options = v.InferOutput<typeof OptionsSchema>
108108
export type OptionName = keyof Options
109109

110110
function getRawOption(optionName: OptionName, cliValue?: string): unknown {
111-
return cliValue ?? config[optionName] ??
112-
Deno.env.get("LINEAR_" + optionName.toUpperCase())
111+
return cliValue ??
112+
Deno.env.get("LINEAR_" + optionName.toUpperCase()) ??
113+
config[optionName]
113114
}
114115

115116
export function getOption<T extends OptionName>(

test/config.test.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,105 @@ Deno.test("getOption - download_images returns undefined for unrecognized string
5252
const result = getOption("download_images", "maybe")
5353
assertEquals(result, undefined)
5454
})
55+
56+
Deno.test("getOption - environment variables take precedence over config file", async () => {
57+
// Create a temp directory with a config file
58+
const tempDir = await Deno.makeTempDir()
59+
const configValue = "from-config-file"
60+
const envValue = "from-env-var"
61+
62+
try {
63+
// Write a .linear.toml with a workspace value
64+
await Deno.writeTextFile(
65+
`${tempDir}/.linear.toml`,
66+
`workspace = "${configValue}"\n`,
67+
)
68+
69+
// Get absolute paths to the config module and deno.json
70+
const configPath = new URL("../src/config.ts", import.meta.url).pathname
71+
const denoJsonPath = new URL("../deno.json", import.meta.url).pathname
72+
73+
// Run a subprocess that imports config and prints the workspace value
74+
// The subprocess runs from the temp directory so it loads our test config
75+
const command = new Deno.Command("deno", {
76+
args: [
77+
"eval",
78+
`--config=${denoJsonPath}`,
79+
`import { getOption } from "file://${configPath}"; console.log(getOption("workspace") ?? "undefined");`,
80+
],
81+
cwd: tempDir,
82+
env: {
83+
LINEAR_WORKSPACE: envValue,
84+
},
85+
stdout: "piped",
86+
stderr: "piped",
87+
})
88+
89+
const { stdout, stderr } = await command.output()
90+
const output = new TextDecoder().decode(stdout).trim()
91+
const errorOutput = new TextDecoder().decode(stderr)
92+
93+
if (errorOutput) {
94+
console.error("Subprocess stderr:", errorOutput)
95+
}
96+
97+
// The env var should win over the config file
98+
assertEquals(
99+
output,
100+
envValue,
101+
"Environment variable should take precedence over config file",
102+
)
103+
} finally {
104+
// Clean up temp directory
105+
await Deno.remove(tempDir, { recursive: true })
106+
}
107+
})
108+
109+
Deno.test("getOption - config file is used when no env var is set", async () => {
110+
// Create a temp directory with a config file
111+
const tempDir = await Deno.makeTempDir()
112+
const configValue = "from-config-file"
113+
114+
try {
115+
// Write a .linear.toml with a workspace value
116+
await Deno.writeTextFile(
117+
`${tempDir}/.linear.toml`,
118+
`workspace = "${configValue}"\n`,
119+
)
120+
121+
// Get absolute paths to the config module and deno.json
122+
const configPath = new URL("../src/config.ts", import.meta.url).pathname
123+
const denoJsonPath = new URL("../deno.json", import.meta.url).pathname
124+
125+
// Run a subprocess without LINEAR_WORKSPACE env var
126+
const command = new Deno.Command("deno", {
127+
args: [
128+
"eval",
129+
`--config=${denoJsonPath}`,
130+
`import { getOption } from "file://${configPath}"; console.log(getOption("workspace") ?? "undefined");`,
131+
],
132+
cwd: tempDir,
133+
env: {}, // No LINEAR_WORKSPACE set
134+
stdout: "piped",
135+
stderr: "piped",
136+
})
137+
138+
const { stdout, stderr } = await command.output()
139+
const output = new TextDecoder().decode(stdout).trim()
140+
const errorOutput = new TextDecoder().decode(stderr)
141+
142+
if (errorOutput) {
143+
console.error("Subprocess stderr:", errorOutput)
144+
}
145+
146+
// The config file value should be used as fallback
147+
assertEquals(
148+
output,
149+
configValue,
150+
"Config file should be used when no env var is set",
151+
)
152+
} finally {
153+
// Clean up temp directory
154+
await Deno.remove(tempDir, { recursive: true })
155+
}
156+
})

0 commit comments

Comments
 (0)