Summary
secrets.providers in nix/generated/openclaw-config-options.nix uses t.oneOf with three t.submodule variants (discriminated by source). This fails nix flake check — Nix's types.oneOf cannot reliably discriminate between submodules that share overlapping option names.
As a result, it's impossible to declare secret providers declaratively (except with source = "env"):
programs.openclaw.instances.default.config.secrets.providers.my-secrets = {
source = "file";
path = "/run/secrets/openclaw_json";
mode = "json";
};
Root Cause
The generated type for secrets.providers (around line 10149) is:
providers = lib.mkOption {
type = t.nullOr (t.attrsOf (t.oneOf [
(t.submodule { options = { source = lib.mkOption { type = t.enum [ "env" ]; }; ... }; })
(t.submodule { options = { source = lib.mkOption { type = t.enum [ "file" ]; }; ... }; })
(t.submodule { options = { source = lib.mkOption { type = t.enum [ "exec" ]; }; ... }; })
]));
default = null;
};
types.oneOf checks each variant in sequence via its check function, but types.submodule's check is very permissive — it accepts any attrset. This means:
- Multiple variants match the input value.
- The
merge function then fails because the value doesn't satisfy all variants simultaneously.
nix flake check (which evaluates all options strictly) surfaces the error, even when the value is perfectly valid for one specific variant.
This is a known limitation of types.oneOf with submodules in nixpkgs — it works for structurally distinct types (e.g., str vs submodule) but not for discriminated-union-style submodules that share keys like source.
Same issue was also flagged here on nixpkgs: NixOS/nixpkgs#337108
Suggested Fix
Instead of generating t.oneOf [ submoduleA submoduleB submoduleC ], the code generator should produce a single t.submodule where:
source remains the discriminator as t.enum [ "env" "file" "exec" ]
- Each variant's fields are nested under a submodule named after the variant, eliminating any overlap between variants
For example:
providers = lib.mkOption {
type = t.nullOr (t.attrsOf (t.submodule { options = {
source = lib.mkOption { type = t.enum [ "env" "file" "exec" ]; };
env = lib.mkOption {
type = t.nullOr (t.submodule { options = {
allowlist = lib.mkOption { type = t.nullOr (t.listOf t.str); default = null; };
}; });
default = null;
};
file = lib.mkOption {
type = t.nullOr (t.submodule { options = {
path = lib.mkOption { type = t.str; };
mode = lib.mkOption { type = t.nullOr (t.enum [ "singleValue" "json" ]); default = null; };
maxBytes = lib.mkOption { type = t.nullOr t.int; default = null; };
timeoutMs = lib.mkOption { type = t.nullOr t.int; default = null; };
}; });
default = null;
};
exec = lib.mkOption {
type = t.nullOr (t.submodule { options = {
command = lib.mkOption { type = t.str; };
args = lib.mkOption { type = t.nullOr (t.listOf t.str); default = null; };
# ... remaining exec-only options
}; });
default = null;
};
}; }));
default = null;
};
Usage would then look like:
secrets.providers.my-secrets = {
source = "file";
file = {
path = "/run/secrets/openclaw_json";
mode = "json";
};
};
This keeps each variant's options cleanly separated with no field overlap, preserves full type safety (required fields like path and command stay non-nullable), and is idiomatic in the Nix module system.
The relevant place to fix this is likely in nix/scripts/generate-config-options.ts, in the oneOf handler — when all entries are submodules sharing a discriminator field, generate a single submodule with nested variant submodules instead of emitting t.oneOf.
Current Workaround
Patch the generated config JSON in a home.activation script after nix-openclaw writes it, using jq to inject secrets.providers outside of the Nix module system:
home.activation.openclawSecretsProviders =
lib.hm.dag.entryAfter [ "openclawConfigFiles" ] ''
config="$HOME/.openclaw/openclaw.json"
if [ -e "$config" ]; then
resolved="$(readlink -f "$config")"
${lib.getExe pkgs.jq} '
.secrets.providers["sops"] = {"source": "file", "path": "/run/secrets/openclaw_json", "mode": "json"}
' "$resolved" >| "$config.tmp"
run --quiet mv "$config.tmp" "$config"
fi
'';
Related
Environment
- nix-openclaw rev:
2b1e71bd01ee7984649ab18a4eaa76ada29d4575
- nixpkgs: NixOS 24.11
- Platform: x86_64-linux
Summary
secrets.providersinnix/generated/openclaw-config-options.nixusest.oneOfwith threet.submodulevariants (discriminated bysource). This failsnix flake check— Nix'stypes.oneOfcannot reliably discriminate between submodules that share overlapping option names.As a result, it's impossible to declare secret providers declaratively (except with
source = "env"):Root Cause
The generated type for
secrets.providers(around line 10149) is:types.oneOfchecks each variant in sequence via itscheckfunction, buttypes.submodule'scheckis very permissive — it accepts any attrset. This means:mergefunction then fails because the value doesn't satisfy all variants simultaneously.nix flake check(which evaluates all options strictly) surfaces the error, even when the value is perfectly valid for one specific variant.This is a known limitation of
types.oneOfwith submodules in nixpkgs — it works for structurally distinct types (e.g.,strvssubmodule) but not for discriminated-union-style submodules that share keys likesource.Same issue was also flagged here on nixpkgs: NixOS/nixpkgs#337108
Suggested Fix
Instead of generating
t.oneOf [ submoduleA submoduleB submoduleC ], the code generator should produce a singlet.submodulewhere:sourceremains the discriminator ast.enum [ "env" "file" "exec" ]For example:
Usage would then look like:
This keeps each variant's options cleanly separated with no field overlap, preserves full type safety (required fields like
pathandcommandstay non-nullable), and is idiomatic in the Nix module system.The relevant place to fix this is likely in
nix/scripts/generate-config-options.ts, in theoneOfhandler — when all entries are submodules sharing a discriminator field, generate a single submodule with nested variant submodules instead of emittingt.oneOf.Current Workaround
Patch the generated config JSON in a
home.activationscript after nix-openclaw writes it, usingjqto injectsecrets.providersoutside of the Nix module system:Related
oneOf-of-consts (fixed), but this is the submodule variant of the same class of problem.Environment
2b1e71bd01ee7984649ab18a4eaa76ada29d4575