Skip to content

cli: configuration is loaded then discarded, so AKROASIS_* env vars abort unrelated commands #461

Description

@forkwright

Finding

The akroasis CLI loads a configuration file and environment overrides, then
discards the result. The configuration surface has exactly two possible effects
on the program: abort it before any command runs, or nothing. It can never
configure anything.

run() binds the loaded config to _config and drops it:

fn run() -> Result<(), Error> {
    let _config = load_config()?;   // loaded, then discarded
    let cli = Cli::parse();
    ...
}

Config carries one field, config_path, which no code reads — load_config
always reads default_config_path() regardless of what config_path says. The
struct is #[serde(deny_unknown_fields)], so every other key is a hard error.

Evidence

crates/akroasis/src/main.rs:133let _config = load_config()?;
crates/akroasis/src/main.rs:59-68Config with deny_unknown_fields, one
never-read field, behind #[expect(dead_code, reason = "config fields reserved for future CLI options, tracked in #264")]
crates/akroasis/src/main.rs:72-82load_config joins
Toml::file(default_config_path()) and Env::prefixed("AKROASIS_")

Behaviour of the built binary (target/debug/akroasis mesh status, run under an
isolated HOME):

Input Exit Result
no config file, no env 0 works
AKROASIS_LOG_LEVEL=debug 1 configuration error: unknown field: found \log_level`, expected ``config_path`` for key "LOG_LEVEL" in `AKROASIS_` environment variable(s)`
config.toml containing log_level = "debug" 1 configuration error: unknown field: found \log_level`, expected ``config_path`` ... TOML file`
AKROASIS_CONFIG_PATH=/nonexistent/nowhere.toml 0 silently ignored — no error for the missing file, default path still used

Why this matters

Two distinct failures, and the second is the one that will reach a user first.

Any AKROASIS_* variable in the environment aborts the CLI. Not the command
being run — the whole binary, before dispatch. A user who exports
AKROASIS_LOG_LEVEL once in a shell profile gets every subsequent invocation
failing with a configuration error, including commands that read no
configuration at all. The same applies to any key placed in the config file the
program itself documents as its config file.

The one accepted key is inert. config_path names the behaviour a reader
would expect — relocate the configuration file — and does not do it. A user who
sets it sees no error and no effect, which is worse than rejection: the program
confirms the setting is valid and ignores it.

Together these invert the intent. The strict schema exists to catch typos, but
the schema it enforces contains only a key that does nothing, so strictness
rejects every correct thing a user might write while accepting the one thing
that has no effect.

This sat behind a dead-code suppression reading "config fields reserved for
future CLI options" — the framing was accurate about the field being unused and
silent about the surface being actively harmful. That is the pattern issue 264
exists to surface.

Desired correction

Pick one and make the surface honest about it:

  1. Wire it. Read config_path before choosing the file to load (a two-pass
    load: resolve the path from CLI/env, then load that file), and add the config
    keys the program actually has. Keeps deny_unknown_fields meaningful.
  2. Remove it. Delete Config, load_config, and the Config error variant
    until there is a setting worth reading. default_config_path and the figment
    dependency go with them.

Recommend 2 for now. There is no second config key to justify the surface,
and option 1 spends design effort on a file nothing reads. Removal makes the
absence of configuration true rather than pretended, and the surface can return
when a real setting exists to put in it.

Either way, AKROASIS_* must stop aborting unrelated commands.

Done when:

  • No AKROASIS_* environment variable, and no content in the default config
    file, can fail a command that does not read configuration.
  • Either config_path demonstrably relocates the loaded file, or it no longer
    exists.
  • The #[expect(dead_code)] on Config is gone rather than re-worded, because
    the struct is either read or absent.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions