Skip to content

Every value a program reads from the user comes from the config system #1025

Open

Description

@David-Noble-at-work

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

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 the DEVLORE_ rows of the table above are #1026's. This issue keeps the rule, the guard, the conventional variables, and the hand reads that are not DEVLORE_ 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.md states the rule, and lists the conventions read directly.
  • The guard runs under make check and 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), VISUAL and EDITOR (the editor), NO_COLOR (color), COLUMNS (the width), the model providers' *_API_KEY (model.api_key), and GH_TOKEN (the token self upgrade sends 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_* and TZ.
  • 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, then PAGER, then the fallback. DEVLORE_PAGER is read by hand (the table above), and it is global while <program>.pager is per program.
  • The editor reads EDITOR before VISUAL (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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions