feat: add --env-file flag to develop.sh#25621
Conversation
|
/coder-agents-review |
|
Review posted | Chat Review historydeep-review v0.5.0 | Round 3 | Last posted: Round 3, 19 findings (2 P0, 2 P2, 10 P3, 5 Nit), COMMENT. Review Finding inventoryFindings
Contested and acknowledged(none) Round logRound 1Panel. Netero first pass: 1 P3, 2 Nit. Panel: 15 reviewers. 1 P2, 5 P3, 3 Nit posted. 1 Nit dropped. Reviewed against 0d9718e..69f5ae4. Round 2Churn guard: PROCEED. All R1 findings addressed. Netero: 2 P0. Panel: 15 reviewers. 2 P0, 1 P2, 3 P3, 2 Nit new. 1 Nit dropped. Reviewed against 0d9718e..b42d851. Round 3Churn guard: PROCEED. All R2 findings addressed (cccdb91, 263bde8). PR description rewritten. Netero: no findings. Panel: 9 reviewers (Bisky, Chopper, Ging-Go, Hisoka, Kite, Mafu-san, Mafuuu, Pariston + Razor wildcard). 5 no findings. 1 P3 new. Reviewed against 0d9718e..263bde8. About deep-reviewCRF = Coder Review Finding (P0-P4, Nit, Note)
|
There was a problem hiding this comment.
Pull request overview
Adds .env file loading to the scripts/develop orchestrator so local dev configuration can be provided via a repo-root .env file before serpent parses Env-tagged options.
Changes:
- Load
.envat process startup and report how many variables were applied (without overriding existing env). - Add a small
.envparser (loadDotEnv) that supports comments,exportprefix, and quoted values. - Add unit tests covering basic loading, non-overriding behavior, and missing-file behavior.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| scripts/develop/main.go | Loads .env before CLI option parsing; introduces loadDotEnv helper for parsing and applying variables. |
| scripts/develop/main_test.go | Adds tests validating .env loading behavior and override semantics. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Clean, well-scoped addition. Zero dependencies, correct sequencing before serpent parsing, correct no-override semantics, and proportional test coverage. Pariston traced four alternative framings of the problem (direnv, full config format, .env.example) and concluded the in-process loader is the right call.
1 P2, 5 P3, 3 Nit. The P2 is the strings.Trim quote-stripping behavior (CRF-4): six reviewers converged on it independently, and empirical verification confirms strings.Trim(val, "\"'") silently corrupts values with mixed quote characters at boundaries.
CRF-4 and CRF-5 interact: KEY="value" # comment produces value" # comment because Trim strips the leading " but the trailing " is no longer at the string edge. If inline comments are stripped first (CRF-5), the quote-matching fix for CRF-4 works correctly on commented lines too. Fix order: inline comment stripping before quote matching.
"The premise of hand-rolling to avoid a dependency is fine for a dev tool. But the parser has to match the common expectations of .env syntax, and inline comments are part of that contract." (Zoro)
🤖 This review was automatically generated with Coder Agents.
Replace the automatic .env loading with an explicit --env-file flag backed by CODER_DEV_ENV_FILE env var. Uses godotenv for parsing instead of a hand-rolled parser. The flag is pre-parsed from os.Args before serpent runs so that loaded variables are visible to serpent's Env-tag resolution for other options.
|
/coder-agents-review |
There was a problem hiding this comment.
Good response to R1. All 10 findings addressed. The switch from a hand-rolled parser to godotenv eliminates the entire class of parsing bugs from R1 (quote stripping, inline comments, empty keys, malformed lines). The new --env-file flag with pre-serpent parsing is a cleaner design than the hardcoded .env path.
2 P0, 1 P2, 3 P3, 2 Nit. The P0s are CI-breaking: missing //nolint:paralleltest on TestParseEnvFileFlag and godotenv listed as // indirect in go.mod despite being directly imported.
R1 findings CRF-1 through CRF-11: all verified fixed. The godotenv switch was the right call.
"The agent knew the right pattern, applied it to one variable, and didn't generalize to the other two in the same block. This round: fixed all three. Self-correction landed." (Mafu-san, observing the R1-to-R2 delta)
🤖 This review was automatically generated with Coder Agents.
| }) | ||
| } | ||
|
|
||
| func TestParseEnvFileFlag(t *testing.T) { |
There was a problem hiding this comment.
P0 [CRF-12] TestParseEnvFileFlag mutates os.Args (process-global) but lacks the //nolint:paralleltest annotation that TestLoadEnvFile (line 859) correctly has. The linter flags this:
scripts/develop/main_test.go:921: Function TestParseEnvFileFlag missing the call to method parallel (paralleltest)
Fix: add //nolint:paralleltest // parseEnvFileFlag reads os.Args, a process-global. before the function declaration.
(Netero, Mafu-san)
🤖
There was a problem hiding this comment.
Fixed in cccdb91.
🤖 Generated with Coder Agents
| github.com/inconshreveable/mousetrap v1.1.0 // indirect | ||
| github.com/jackmordaunt/icns/v3 v3.0.1 // indirect | ||
| github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect | ||
| github.com/joho/godotenv v1.5.1 // indirect |
There was a problem hiding this comment.
P0 [CRF-13] github.com/joho/godotenv is imported directly in scripts/develop/main.go:30 but listed as // indirect here. Running go mod tidy moves it to the direct dependencies block, producing a dirty diff that fails CI gen/fmt checks.
(Netero, Mafu-san)
🤖
There was a problem hiding this comment.
Fixed in cccdb91. Removed // indirect marker.
🤖 Generated with Coder Agents
| ) | ||
|
|
||
| func main() { | ||
| // Pre-parse --env-file before serpent runs so that variables from |
There was a problem hiding this comment.
P2 [CRF-14] The PR description still describes R1's auto-load behavior: "On startup, the develop binary reads .env from the repo root." The code now requires explicit --env-file or CODER_DEV_ENV_FILE. The title, behavior section, and example .env block all need updating to reflect the opt-in --env-file design shown here.
(Leorio P2, Mafu-san P2)
🤖
There was a problem hiding this comment.
Fixed. PR title and description updated to reflect the opt-in --env-file design.
🤖 Generated with Coder Agents
| // are visible to serpent's Env-tag resolution for other options. | ||
| func parseEnvFileFlag() string { | ||
| for i, arg := range os.Args[1:] { | ||
| if arg == "--env-file" && i+2 < len(os.Args) { |
There was a problem hiding this comment.
P3 [CRF-15] When --env-file is the last argument with no value (os.Args = ["develop", "--env-file"]), the bounds check i+2 < len(os.Args) fails and the function silently falls through to os.Getenv("CODER_DEV_ENV_FILE"). The developer typed --env-file intending to pass a path, but no diagnostic indicates the flag was ignored.
"The developer sees either 'develop: error loading env file' (if the env var is set to a bad path) or nothing at all (if the env var is unset). Neither message mentions the dangling flag." (Chopper)
Consider either erroring when --env-file appears without a value, or adding a test that pins the current fall-through behavior as intentional.
(Bisky P3, Chopper P3, Meruem P3)
🤖
There was a problem hiding this comment.
Fixed in 263bde8. parseEnvFileFlag now prints "develop: --env-file requires a value" and exits 1 when --env-file is the last argument. Added subprocess test ErrorsWhenValueMissing to cover this.
🤖 Generated with Coder Agents
| // not already present in the process environment. It returns the number of | ||
| // variables set. | ||
| func loadEnvFile(path string) (int, error) { | ||
| vars, err := godotenv.Read(path) |
There was a problem hiding this comment.
P3 [CRF-16] godotenv.Read expands $VAR and ${VAR} references in unquoted and double-quoted values. Undefined variables resolve to empty string. Single-quoted values are not expanded.
This is new behavior vs. the R1 hand-rolled parser, which did not expand variables. A developer writing CODER_EXTERNAL_AUTH_0_CLIENT_SECRET=$ecret_value will get an empty secret (godotenv interprets $ecret_value as variable reference ecret_value, which is undefined). The --env-file help text or a code comment should note this. Workaround: single-quote values containing $.
Verified against godotenv's test suite (TestExpanding): "expands undefined variables to an empty string."
(Hisoka)
🤖
There was a problem hiding this comment.
Documented in 263bde8. The --env-file option description now notes: "unquoted and double-quoted values undergo $VAR expansion; use single quotes for literal dollar signs." This is standard .env behavior and we're keeping it.
🤖 Generated with Coder Agents
| require.Error(t, err) | ||
| }) | ||
|
|
||
| t.Run("EmptyPathNoAction", func(t *testing.T) { |
There was a problem hiding this comment.
P3 [CRF-17] Test name EmptyPathNoAction implies the function takes no action, but the test asserts require.Error(t, err). The name should match the assertion: ErrorsOnEmptyPath.
(Gon)
🤖
There was a problem hiding this comment.
Fixed in 263bde8. Renamed to ErrorsOnEmptyPath.
🤖 Generated with Coder Agents
| _, _ = fmt.Fprintf(os.Stderr, "develop: error loading env file %q: %v\n", envFile, err) | ||
| os.Exit(1) | ||
| } | ||
| _, _ = fmt.Fprintf(os.Stderr, "develop: loaded %d variable(s) from %s\n", n, envFile) |
There was a problem hiding this comment.
Nit [CRF-18] Line 80 uses %q (quoted path) in the error message; line 83 uses %s (bare path) in the success message. Same variable, same stderr, 3 lines apart. Pick one format for consistency.
(Chopper, Gon, Leorio)
🤖
There was a problem hiding this comment.
Fixed in 263bde8. Both messages now use %s.
🤖 Generated with Coder Agents
| dbRollback bool | ||
| dbReset bool | ||
| dbContinue bool | ||
| envFile string |
There was a problem hiding this comment.
Nit [CRF-19] envFile is written by serpent but never read. The caller comment at lines 73-76 explains this design (pre-parse + --help discoverability), but a reader looking at the struct definition has no signal that the field is intentionally write-only. A brief comment on the field would help:
// envFile is populated by serpent for --help; the actual loading
// uses parseEnvFileFlag() before serpent runs.
envFile string(Ryosuke Nit, Meruem P3)
🤖
There was a problem hiding this comment.
Fixed in 263bde8. Added comment on the struct field explaining the pre-parse design.
🤖 Generated with Coder Agents
|
/coder-agents-review |
There was a problem hiding this comment.
All R2 findings addressed and verified. Good work across three rounds: the hand-rolled parser bugs from R1 are gone, CI-breaking issues from R2 are fixed, the PR description matches the implementation, and the dangling-flag edge case is handled.
1 P3 from the panel. 20 of 20 prior findings are resolved. The remaining item is a documentation refinement.
CI note: the title check is still failing. Worth resolving before merge.
"godotenv.Read returns a map. It never calls os.Getenv. The expansion scope is file-local only, and the docs don't say that." (Hisoka)
🤖 This review was automatically generated with Coder Agents.
| { | ||
| Flag: "env-file", | ||
| Env: "CODER_DEV_ENV_FILE", | ||
| Description: "Path to a .env file to load before starting. Variables in the file do not override existing environment variables. Note: unquoted and double-quoted values undergo $VAR expansion; use single quotes for literal dollar signs.", |
There was a problem hiding this comment.
P3 [CRF-21] The Description documents $VAR expansion and the single-quote workaround, but omits the expansion scope: godotenv.Read() resolves $VAR references only against other variables defined earlier in the same file, not the process environment.
A developer writing MYPATH=$HOME/.config expects shell-like behavior ($HOME from the process env). With godotenv.Read(), $HOME resolves to empty string because it's not defined in the file. No error, no warning.
"godotenv.Read returns a map and does not call os.Getenv; only godotenv.Load (which this PR does not use) consults the process environment." (Razor, verified against parser.go:157,177)
Suggested addition to the Description: "Variables are expanded against other entries in the same file, not the process environment."
(Hisoka P3, Razor P3)
🤖
…s.Exit Satisfies revive deep-exit rule by removing os.Exit from non-main function.
Adds opt-in
.envfile support to the develop orchestrator (scripts/develop) via a new--env-fileflag. This makes it easier to configure things like external auth providers,access URLs, and other dev-time settings without exporting a wall of environment variables
in every shell session.
Behavior
--env-file <path>or setCODER_DEV_ENV_FILEto load a.envfile.CODER_DEV_ACCESS_URLandCODER_DEV_PORTwork from the env file.github.com/joho/godotenvfor parsing (supports all standard.envsyntax).develop: loaded N variable(s) from <path>to stderr when active.$VARexpansion;use single quotes for literal dollar signs.
Usage
Example
.envCODER_DEV_ACCESS_URL=http://dev.myworkspace.me.coder:3000 CODER_EXTERNAL_AUTH_0_ID=primary-gitlab CODER_EXTERNAL_AUTH_0_TYPE=gitlab CODER_EXTERNAL_AUTH_0_CLIENT_ID=abc123 CODER_EXTERNAL_AUTH_0_CLIENT_SECRET='secret$pecial'Design decisions
godotenvlibrary