Skip to content

Every environment variable carries its program name twice: WRIT_WRIT_VERBOSE works, WRIT_VERBOSE does not #763

Open

Description

@David-Noble-at-work

Feature: #456 — Configuration surfaces in the CLIs

Every environment variable in the suite carries its program's name twice. WRIT_WRIT_VERBOSE works;
WRIT_VERBOSE does not.

Measured

$ WRIT_VERBOSE=true      writ status -o json    # no effect
$ WRIT_WRIT_VERBOSE=true writ status -o json    # "Using config: ..." — it took

Cause

Two mechanisms each add the program name, and neither knows about the other.

--verbose                                        the flag
  ↓  BindFlags: key = toolName + "." + f.Name         cmd/internal/cli/viper.go:113
writ.verbose                                     the viper key
  ↓  SetEnvKeyReplacer(".", "_") then upper-cased     viper.go:81
WRIT_VERBOSE
  ↓  SetEnvPrefix("WRIT") prepends                    viper.go:79
WRIT_WRIT_VERBOSE                                the variable a user must set

BindFlags namespaces the key because UseSharedConfig puts all four programs in one config file, where
writ.verbose and lore.verbose must not collide. InitViper then sets the env prefix to the same program
name. Each is right on its own.

The documented contract says otherwise, in two places

cmd/internal/cli/viper.go:80:

Replace dots with underscores for nested keys: writ.vars.user_name → WRIT_VARS_USER_NAME

schema/devlore-config.json:122:

Example: model.provider → DEVLORE_MODEL_PROVIDER

Both describe single prefixing. Neither matches the binary. RULED 2026-08-31: the binary is wrong.

Scope: all four programs, every flag

Any persistent flag bound through BindFlags is affected — --config, --verbose, --output, --store,
--filter, --jq, and the model flags, on devlore-test, lore, star, and writ.

The one env path that behaves as documented is WRIT_SEGMENT_<NAME>, and only because
segment.LoadFromEnv reads os.Getenv directly (detect.go:39) rather than going through viper.

Fix

Strip the program name from one of the two. The key must stay namespaced — the shared config file requires
it — so the env prefix is what goes: SetEnvPrefix becomes DEVLORE, or is dropped in favour of a key
replacer that maps writ.verbose to WRIT_VERBOSE directly.

DEVLORE as the prefix has a second argument for it: schema/devlore-config.json:122 already documents
DEVLORE_MODEL_PROVIDER, so shared keys read correctly, and a program-scoped key becomes
DEVLORE_WRIT_VERBOSE — which says what it is.

This is breaking

Every environment variable in use today stops working. Under the governing principle a variable retires by
deletion rather than by accepting both spellings — an alias here would mean carrying the doubled form
forever.

Worth knowing what is actually at risk: WRIT_WRIT_* is undocumented and contradicts both doc comments, so
anything relying on it was written by reading the source or by trial and error.

Verification

  • WRIT_VERBOSE=true writ status reports the config file in use — or DEVLORE_WRIT_VERBOSE, per the shape
    chosen.
  • The same flag on lore, star, and devlore-test resolves under its own program's name with no doubling.
  • A shared key resolves once, not once per program.
  • WRIT_SEGMENT_<NAME> still works — it does not route through viper.
  • A test asserts the mapping, so the contract stops being maintained by reading two comments that disagree
    with the binary.

Related

Found while designing --scope for #762, which needs a working environment path. Same class as #746, #753,
and #754: a declared surface the code does not honour.

Activity

  1. added
    bugSomething isn't working
    Area:CLICommand-line interface
    taskImplementation work item
    and removed
    taskImplementation work item
    on Sep 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions