Configuration precedence, from lowest to highest, is:
- One automatically discovered base winner: user configuration, otherwise system configuration, otherwise built-in defaults.
- Project
.netsuke.toml. NETSUKE_environment variables.- Explicit command-line options.
System and user configuration are discovered from platform conventions rather
than merged as two separately named Netsuke layers. On Unix, this means the XDG
base directories and the home directory; on Windows it means the
application-data directories, such as %APPDATA%\netsuke\config.toml.
Automatic discovery chooses one exclusive winner among system configuration,
user configuration, and built-in defaults. Netsuke then appends the project
.netsuke.toml layer, so project values can override the winner while fields
present only in the winner remain available.
An explicit selector bypasses automatic discovery. Selectors are checked in this order:
--config <PATH>NETSUKE_CONFIG=<PATH>
An explicit file that is missing or invalid causes an error; Netsuke does not fall back to discovery.
When automatic discovery finds no configuration file, Netsuke uses its built-in
defaults. When it finds a candidate that cannot be loaded, such as malformed
TOML or a file whose extends parent is missing, Netsuke reports the load
error. A broken discovered configuration is therefore not treated as absent.
On Windows, Netsuke normalizes alternate spellings of a configuration path,
including short and long path forms, before comparing discovered layers. A
project .netsuke.toml therefore contributes one layer even when two spellings
refer to the same physical file.
Diagnose configuration selection
Pass --verbose to see how Netsuke selected its configuration. Structured
events report whether --config, NETSUKE_CONFIG, or automatic discovery won,
whether a path was present, and which environment lookups were attempted.
Events then identify whether Netsuke uses an explicit file or discovered layers.
During the merge, verbose tracing also reports the defaults, file, environment,
and CLI layers as they are applied. File-layer events include a bounded
path_hash so operators can correlate a layer with discovery events without
recording the raw path. CLI events record only the leaf keys in
override_keys; they do not record override values such as paths or host
lists. If validation rejects the merged configuration, the event includes the
rejected setting in key and a bounded explanation in reason. These events
make configuration precedence and rejection decisions auditable without
exposing user-supplied values.
If an explicit file cannot be loaded, the warning records failure_kind as
Missing or LoadError. Verbose tracing uses only path_hash and
path_present; it never exposes a file name or full path. The unkeyed
path_hash is only a correlation identifier: it does not protect a guessable
path from disclosure.
Configuration tracing is disabled in JSON mode, including when json = true
comes from a configuration file. This keeps stderr empty for successful JSON
commands and reserves it for the single diagnostic document on failure.
For a terminal human-mode failure in either the early diagnostic-mode
preference pass or the full config_merge phase, the
configuration load failed event includes bounded operation and
error_category fields. JSON mode instead emits the diagnostic document.
Passing --verbose additionally emits one final metrics snapshot debug event
before Netsuke exits. The snapshot is an in-process diagnostic record, not a
metrics listener or a Prometheus endpoint. After a successful configuration
merge, verbosity can also come from NETSUKE_VERBOSE or verbose = true in a
configuration file. A configuration failure before that merge uses only CLI
--verbose.
It includes the bounded configuration-load series:
netsuke_config_load_total, withoutcome=successoroutcome=failure.netsuke_config_load_duration_seconds, with one sample for the startup configuration-load attempt.- The phase-level
config_load_totalandconfig_load_duration_secondsentries, labelled withphase=diag_modeorphase=merge; the counter also carries the bounded outcome value. netsuke_cli_config_discovery_total, withoutcome=successoroutcome=error, andnetsuke_cli_config_discovery_duration_seconds, which records the cached discovery pass duration.netsuke_cli_path_validation_total, with boundedsource=fileorsource=directoryandreason=non_utf8, counts raw CLI path rejections. When parsing exits early for one of these rejections, the counter is not present in a shutdownmetrics snapshot.
For example, a missing explicit file reports the actionable error first, then the bounded tracing fields and the snapshot (timestamps and metric values vary):
Configuration file error in 'missing.toml': explicit configuration file not found
ERROR ... configuration load failed operation="diag_mode_resolution" error_category="io"
DEBUG ... metrics snapshot metrics=[...]
The snapshot is available for a configuration failure when --verbose was
supplied on the command line. A verbose = true setting in a file that cannot
be loaded cannot enable diagnostics because configuration merging has not
completed. JSON mode suppresses the tracing and snapshot so stderr remains one
machine-readable diagnostic document.
Cached merge API (unstable)
Programs using Netsuke's unstable Rust API can retain the layers from one
discovery pass and observe the subsequent merge. Construct
CachedMergeInput::new(cli, matches, env, discovered) with the parsed CLI
values, an injected ConfigEnvProvider, and DiscoveryOutcome::into_layers();
then pass it to cli::merge_with_cached_file_layers_with_observer(input). The
function returns the merge result alongside bounded events; replay those events
through MergeObserver, such as TracingMergeObserver. Another caller can
provide its own MergeObserver implementation. Observers receive bounded
MergeEvent values: layer application and failure states, file path_hash and
layer counts, CLI override leaf keys, and validation key/reason fields.
Configuration values and raw paths are never included. Ordinary
merge_with_config* and merge_with_cached_file_layers calls discard their
collected events and do not emit merge tracing.
The observer-based cached discovery and merge flow described here is a beta3
improvement. Published beta2 already provides merge_with_cached_file_layers,
but not this observer-based flow.
Bounded configuration metrics
Configuration loading is recorded as two bounded metric series, both emitted in
the drained metrics snapshot:
config_load_total— a counter with thephaseandoutcomelabels that counts each configuration-loading phase.phaseisdiag_modefor the early diagnostic-mode resolution ormergefor the full configuration merge;outcomeissuccessorfailure.config_load_duration_seconds— a histogram with thephaselabel only, recording each phase's duration in seconds.netsuke_cli_config_discovery_total— a counter with a boundedoutcomelabel ofsuccessorerrorfor the discovery pass reused by startup.netsuke_cli_config_discovery_duration_seconds— a histogram recording the discovery pass duration without labels.
Legacy recipe operation metrics
Legacy recipe execution emits two bounded metric series in the drained
metrics snapshot:
netsuke_runner_legacy_recipe_executions_totalcounts completed build and Ninja-tool runner operations.netsuke_runner_legacy_recipe_execution_duration_secondsrecords the full duration of each such operation, including shell validation, graph lowering, Ninja generation, and Ninja invocation.
Both series use the same fixed labels. operation is build or ninja_tool;
recipe_shell is posix, powershell, or bash; outcome is success or
error; and failure_category is none, manifest, graph,
ninja_generation, ninja_io, or other. The ninja_tool value identifies a
Ninja-tool operation and does not imply that the tool executes a recipe. Metric
labels contain no manifest-controlled or process-controlled values.
The annotated sample configuration lists every key. A small project configuration looks like this:
jobs = 4
verbose = true
locale = "en-US"
json = false
no_input = true
color = "never"
emoji = "never"
progress = "never"
accessibility = "on"
default_targets = ["hello.txt"]
Common environment equivalents include:
NETSUKE_JOBS=4NETSUKE_VERBOSE=trueNETSUKE_JSON=falseNETSUKE_NO_INPUT=trueNETSUKE_COLOR=neverNETSUKE_EMOJI=neverNETSUKE_PROGRESS=neverNETSUKE_ACCESSIBILITY=onNETSUKE_LOCALE=en-USNETSUKE_DEFAULT_TARGETS__0=hello.txtNETSUKE_NINJA=/opt/ninja/bin/ninjaNETSUKE_WHICH_WORKSPACE=0
NETSUKE_LOCALE selects the interface language; see
Choose a language with --locale for how
it combines with the flag and the system default.
NETSUKE_NINJA overrides the Ninja executable used by build and clean.
Leave it unset to use ninja from PATH, or set another executable name or an
absolute path. Empty and non-UTF-8 values fall back to the default.
Diagnose Ninja execution
Pass --verbose to see Ninja subprocess diagnostics on stderr. The
Executing Ninja subprocess informational event contains stable fields:
operation, ninja_program, arg_count, env_override_count,
path_overridden, and suppress_stderr. A debug-level companion event
contains the redacted command as Executing command: ..., including the
arguments used for the invocation.
Ninja executable resolution also emits a debug event with ninja_program and
source. The source is NETSUKE_NINJA for a valid override and fallback
when the variable is unset, empty, or non-UTF-8. JSON mode suppresses these
tracing events so stderr remains parseable.
NETSUKE_WHICH_WORKSPACE switches off the which() workspace-tree fallback
search that runs when a command is not found on PATH. Set it to 0, false,
or off (case-insensitively) to disable the fallback; any other value, or
leaving it unset, keeps the fallback enabled. A non-Unicode value also disables
the fallback and is treated as an explicit opt-out, emitting a warning.
Policy values and parsing
The CLI and configuration use the same policy values, and policy names are matched case-insensitively in both places. The accepted values are:
--colorandcolor:auto,always, ornever.--emojiandemoji:auto,always, ornever.--progressandprogress:auto,always, ornever.--accessibilityandaccessibility:auto,on, oroff.
Lowercase spellings are used in help and examples. For instance,
--color ALWAYS and color = "AlWaYs" select the same explicit colour policy
as --color always and color = "always". auto follows terminal and
environment detection. always or never makes colour, emoji, or progress
behaviour explicit. Accessibility uses on and off for its explicit values.
Netsuke has no interactive mode. It never prompts, and no_input = false is
rejected. Pass root --no-input in automation to state that requirement
explicitly and make the invocation self-documenting.