Configure Netsuke

Updated Oct 16, 2026

Configuration precedence, from lowest to highest, is:

  1. One automatically discovered base winner: user configuration, otherwise system configuration, otherwise built-in defaults.
  2. Project .netsuke.toml and its extends chain.
  3. NETSUKE_ environment variables.
  4. 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 configuration chain, so ordinary project values can override the winner while fields present only in the winner remain available.

Fetch-policy fields are the security-sensitive exception to this ordinary precedence. Every occurrence selected through the project .netsuke.toml and its extends chain is a project request, so its grants remain quarantined unless an operator enables trust_project_fetch_policy. Automatic discovery also removes project-chain env_allow_var entries before policy composition. An explicit --config selector bypasses automatic discovery. If its selected layers include the project .netsuke.toml, that file and its inherited parents remain project-scoped for fetch and environment allow policy; an unrelated explicit configuration chain retains operator authority. A file reached from both project and trusted configuration roots retains each occurrence's authority. fetch_block_host and env_block_var remain cumulative because they only add restrictions.

An explicit selector bypasses automatic discovery. Selectors are checked in this order:

  1. --config <PATH>
  2. NETSUKE_CONFIG=<PATH>

An explicit file that is missing or invalid causes an error; Netsuke does not fall back to discovery.

When an explicit --config chain contains the matching project .netsuke.toml, that project occurrence and its inherited parents are project-scoped for fetch and environment allow policy. An unrelated explicit configuration chain remains operator-scoped.

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.

Resource budgets

Netsuke bounds each manifest evaluation before it runs a build command or answers help targets. The defaults are 1,000,000 MiniJinja instructions per evaluation, 100,000,000 per manifest, 1 MiB per rendered value, 16 MiB aggregate rendered output, 4 MiB template source, 10,000 values per foreach, and 50,000 expanded entries. Each evaluation reserves at most the per-evaluation fuel limit. Successful expressions, macro calls, and renders refund unused fuel to the aggregate manifest allowance; exhausted evaluations do not.

These budgets reduce, rather than eliminate, resource-exhaustion exposure. Byte limits are enforced before the buffer Netsuke controls accepts output, but MiniJinja materializes a macro result before its size can be checked, so a macro can allocate above the per-value ceiling before the limit is reported. That residual gap and its deferral are recorded in ADR-018.

MiniJinja 2.24.0 also rejects one repeated string above 100 MB with the repeated string is too large template error. This rejection happens before Netsuke can apply manifest_rendered_value_bytes or manifest_rendered_manifest_bytes to a completed value. It guards only that engine operation: Netsuke's resource budgets remain the controls for rendered output, and the rejection does not close the aggregate macro or capture buffering gap.

The configuration keys are manifest_evaluation_fuel, manifest_fuel, manifest_rendered_value_bytes, manifest_rendered_manifest_bytes, manifest_source_bytes, manifest_foreach_cardinality, and manifest_expanded_entries. Set them in a trusted configuration file, through the matching environment variables (NETSUKE_MANIFEST_EVALUATION_FUEL, NETSUKE_MANIFEST_FUEL, NETSUKE_MANIFEST_RENDERED_VALUE_BYTES, NETSUKE_MANIFEST_RENDERED_MANIFEST_BYTES, NETSUKE_MANIFEST_SOURCE_BYTES, NETSUKE_MANIFEST_FOREACH_CARDINALITY, and NETSUKE_MANIFEST_EXPANDED_ENTRIES), or with the corresponding flags (--manifest-evaluation-fuel, --manifest-fuel, --manifest-rendered-value-bytes, --manifest-rendered-manifest-bytes, --manifest-source-bytes, --manifest-foreach-cardinality, and --manifest-expanded-entries). Project .netsuke.toml values, including values supplied through its extends chain, may narrow a ceiling but cannot widen a value already established by defaults, user configuration, the environment, or the CLI. Invalid or non-positive budget values are rejected as configuration errors rather than ignored. A resource-limit failure reports Manifest resource budget exhausted with its fixed stage and numeric limit, never template text, context values, or rendered secrets. At the runner boundary, this failure has the stable JSON diagnostic code netsuke::runner::manifest_budget_exceeded; other manifest diagnostics retain their existing codes and error handling.

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, with outcome=success or outcome=failure.
  • netsuke_config_load_duration_seconds, with one sample for the startup configuration-load attempt.
  • The phase-level config_load_total and config_load_duration_seconds entries, labelled with phase=diag_mode or phase=merge; the counter also carries the bounded outcome value.
  • netsuke_cli_config_discovery_total, with outcome=success or outcome=error, and netsuke_cli_config_discovery_duration_seconds, which records the cached discovery pass duration.
  • netsuke_cli_path_validation_total, with bounded source=file or source=directory and reason=non_utf8, counts raw CLI path rejections. When parsing exits early for one of these rejections, the counter is not present in a shutdown metrics 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.

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 the phase and outcome labels that counts each configuration-loading phase. phase is diag_mode for the early diagnostic-mode resolution or merge for the full configuration merge; outcome is success or failure.
  • config_load_duration_seconds — a histogram with the phase label only, recording each phase's duration in seconds.
  • netsuke_cli_config_discovery_total — a counter with a bounded outcome label of success or error for 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_total counts completed build and Ninja-tool runner operations.
  • netsuke_runner_legacy_recipe_execution_duration_seconds records 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.

Manifest structure metrics

Manifest structural summaries emit one further bounded series in the drained metrics snapshot:

  • netsuke_runner_manifest_structures_total — an unlabelled counter that counts the manifest structural summaries the runner emits while loading a manifest for graph generation.

The summary records only fixed integer collection counts: variables, macros, rules, actions, targets, and defaults. Manifest text, paths, recipe contents, variable values, macro bodies, and descriptions are never recorded, because rendered manifest values can carry secret material interpolated through env().

Manifest environment-lookup metrics

Loading a manifest counts every env() lookup in one bounded series:

  • netsuke_manifest_env_lookups_total — a counter with a single outcome label that counts each env() lookup. outcome is success when the lookup produced a value, blocked when the environment access policy denied the name, not_present when the variable is absent and no default is available, and not_unicode when its value is not valid UTF-8.

The outcome tracks whether a value was produced, not whether the variable was present. A missing variable resolved through a supplied default is therefore counted as success: the manifest asked for a substitution and received one. Only a missing variable with no fallback available counts as not_present, so an absent variable does not by itself imply that outcome.

The four outcomes are the complete vocabulary; a substituted fallback is not a fifth. That case is instead marked by a bounded fallback_used field on a tracing::debug! event emitted only on the substitution path. The field is not a metric outcome and carries no name or value of its own, but it lets an operator see an exported variable stop propagating even though the lookup still counts as a success.

The blocked outcome is what makes an effective policy measurable: it is the rate at which the policy is refusing manifest access. Variable names and their values never appear in the label, and neither do the contents of a fallback, because environment variable names routinely identify credentials.

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=4
  • NETSUKE_VERBOSE=true
  • NETSUKE_JSON=false
  • NETSUKE_NO_INPUT=true
  • NETSUKE_COLOR=never
  • NETSUKE_EMOJI=never
  • NETSUKE_PROGRESS=never
  • NETSUKE_ACCESSIBILITY=on
  • NETSUKE_LOCALE=en-US
  • NETSUKE_DEFAULT_TARGETS__0=hello.txt
  • NETSUKE_NINJA=/opt/ninja/bin/ninja
  • NETSUKE_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 explicitly requested cwd_mode="workspace-recursive" workspace-tree search. Set it to 0, false, or off (case-insensitively) to disable that search; any other value, or leaving it unset, keeps the recursive mode available. A non-Unicode value also disables it 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:

  • --color and color: auto, always, or never.
  • --emoji and emoji: auto, always, or never.
  • --progress and progress: auto, always, or never.
  • --accessibility and accessibility: auto, on, or off.

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.