Repository navigation
Every value a program reads from the user comes from the config system #1025
Description
Every value a program reads from the user is a setting in a section of the config system, resolved by one chain: its default, overridden by the configuration file, overridden by its environment variable, overridden by its flag. Only the config system reads devlore's environment variables. Today eight places read devlore's own values by hand, beside the config system.
Ruled 2026-10-03
Planning #927's phase 3, Claude proposed that the variable resolver read the environment itself and that the unknown-variable warning take its names from a hand-kept table. The owner: "we are using the config system to read variables "section". the sources of these settings as we've discussed MANY times is...i was going to tell you. you tell me. we are redesigning config, but the pattern will always be the same." Asked where the rule belongs: "this feels like a devlore-cli issue. it applies to the config system and offers design/implementation instructions." Then: "yes, file it. add it to your schedule, and put it high in the list of issues to fix." Asked which conventional variables stay outside the config system: "we should respect the environment variables that apply to our apps. this is the same as git and other well-known apps."
The rule
- A value a program reads from the user lives in a section and is read through the config system: viper today, devconfig after the redesign (
docs/architecture/configuration.md). Its environment variable comes from its key by The environment prefix is doubled: WRIT_WRIT_VERBOSE works and WRIT_VERBOSE does not; the variable is the prefix plus the bare key, for every program #927's rule; no code reads it by name. - A design that reads a user value from anywhere else (the environment directly, a flag map, a table kept beside the code) is wrong until the owner says otherwise. A needed value that no section holds is a missing setting, put to the owner.
- Variables that other programs and the platform own are respected as git respects them (below).
Where the code breaks it
Read at 416642b1, every os.Getenv, os.LookupEnv and os.Environ outside tests:
| Where | Read by hand | Owner |
|---|---|---|
cmd/internal/config/config.go:125-150 (applyEnvOverrides) |
DEVLORE_VERBOSITY, DEVLORE_DRY_RUN, DEVLORE_MODEL_*, DEVLORE_REGISTRY_*: the global settings' environment layer, written by hand |
#1026 with #1010, in lane 8 |
pkg/op/variable_resolver.go:331 |
<PREFIX>_<NAME> |
#927, phase 3 (its ruling 17) |
pkg/op/provider/template/provider.go:110 |
a template's Env is os.Getenv |
#683 (lane 40) |
cmd/writ/writ/segment/detect.go:60 |
WRIT_SEGMENT_<NAME>, scanning os.Environ() |
this issue; #1024 renames the key |
cmd/internal/cli/pager.go:94 |
DEVLORE_PAGER, ahead of <program>.pager |
#1026, in lane 8 |
cmd/internal/cli/selfupgrade_channel.go:200 |
DEVLORE_VERSION, the upgrade pin, which the installers read too |
#1026, in lane 8 |
cmd/writ/writ/identity/identity.go:28 |
AGE_IDENTITY, writ's own variable (neither age nor SOPS defines it), beside the documented secrets.identity_file and secrets.identity_command |
this issue |
pkg/op/default_funcs.go:119 |
{{ env "NAME" }}, a default-value function nothing uses today |
this issue |
cmd/devlore-index/main.go:148 |
DEVLORE_REGISTRY, in a developer tool outside the four programs |
#1026, in lane 8 |
Amended 2026-10-03: every
DEVLORE_setting moves into the config system in lane 8 (#1026, ruled the same day: "EVERY ONE of the DEVLORE_ settings must be in config. add that task to lane 8."), so theDEVLORE_rows of the table above are #1026's. This issue keeps the rule, the guard, the conventional variables, and the hand reads that are notDEVLORE_variables.
The guard
#927's collision test already scans the source for environment reads (cmd/internal/cli/environment_test.go). It grows into a guard: it fails on any read of a name under DEVLORE_ or a program's prefix outside the config system, and on any os.Environ() scan, with only the listed conventions passing. Rows owned by other issues stand as exceptions naming their owner, and each fails once it no longer holds, as the collision test's lane-24 exception does.
Acceptance
-
docs/architecture/configuration.mdstates the rule, and lists the conventions read directly. - The guard runs under
make checkand fails on a hand-read devlore variable. - This issue's rows read through the config system, or are retired.
- Every row owned elsewhere is in the guard's exception list, naming its owner.
Conventional variables (ruled 2026-10-03)
"we should respect the environment variables that apply to our apps. this is the same as git and other well-known apps."
Git's pattern: a conventional variable is the lowest layer of the setting it serves, above the built-in default and beneath the configuration file, and the app's own variable sits above the file. Git's pager is GIT_PAGER, then core.pager, then PAGER, then less; its editor is GIT_EDITOR, then core.editor, then VISUAL, then EDITOR, then vi. How a counterpart is read is the mechanism the owner will settle: docs/architecture/configuration.md § Settings with counterparts in the environment notes that the configuration system might formalize it (noted 2026-10-03).
- Variables that serve one of our settings become its lowest layer:
PAGER(the pager),VISUALandEDITOR(the editor),NO_COLOR(color),COLUMNS(the width), the model providers'*_API_KEY(model.api_key), andGH_TOKEN(the tokenself upgradesends to GitHub). - Variables that locate things or describe the platform are read where the platform defines them:
HOME,USERPROFILE,USER,TMPDIR,TEMP, the XDG variables, the Windows folders (APPDATA,LOCALAPPDATA,ProgramData,ProgramFiles,SystemDrive),TERM,LANG,LC_*andTZ. - Variables that other tools and libraries read for themselves are left to them: the proxy and certificate variables (Go's HTTP client),
SOPS_AGE_KEY*(SOPS),GIT_*(git),GODEBUG(the Go runtime).
Two findings against it, read at 416642b1:
- The pager already follows git's order (
cmd/internal/cli/pager.go:93-107):DEVLORE_PAGER, then<program>.pager, thenPAGER, then the fallback.DEVLORE_PAGERis read by hand (the table above), and it is global while<program>.pageris per program. - The editor reads
EDITORbeforeVISUAL(cmd/internal/cli/config.go:423-425), the reverse of git's order, with no setting above either.
Whether the editor and color get settings of their own is this issue's plan's to settle; the order follows git's either way.
Related
#927 (the naming rule, the collision test, ruling 17), #1010, #683, #1023, #1024, #694 (how configuration reaches a run).
Plan and design documents
| Kind | Document |
|---|---|
| Plan | Not yet opened |
| Design | docs/architecture/configuration.md |
Epic: #441 · Feature: #455 · Schedule: #916, lane 42, PR C′, first
Activity
docs: variables are a section read through the config system; setting…
docs(plan): lane 8 puts every DEVLORE_ setting in the config system (#…