Skip to content

bug: typed submodule null-defaults silently overwrite baseConfig values via recursiveUpdate #89

@nikzasel

Description

@nikzasel

Bug Description

When using programs.openclaw.config = {} (default, empty) or instances.<name>.config = {},
the baseConfig values (e.g. gateway.mode = "local") are silently discarded from the
generated openclaw.json.

Root Cause

In nix/modules/home-manager/openclaw/config.nix (around line 101):

mergedConfig0 = stripNulls (
  lib.recursiveUpdate (lib.recursiveUpdate baseConfig cfg.config) inst.config
);

Both cfg.config and inst.config are declared as lib.types.submodule { options = openclawLib.generatedConfigOptions; }.

When set to {}, Nix evaluates all schema options to their defaults — most of which are
null. The resulting attrset looks like:

{ "gateway": { "mode": null, "port": null, ... }, "channels": null, ... }

Then lib.recursiveUpdate baseConfig cfg.config deep-merges this into baseConfig.
Since cfg.config.gateway.mode = null exists as a key, it overwrites
baseConfig.gateway.mode = "local". The outer stripNulls then removes the null,
so gateway.mode disappears from the final JSON entirely.

Affected Versions

Reproduced on commit 0f4d0666f1490cbe1ba76062275eb22d2f89cc44.

Reproduction

programs.openclaw = {
  enable = true;
  # config = {} is the default — no explicit config set
};

Generated ~/.openclaw/openclaw.json will not contain gateway.mode.
Expected: { "gateway": { "mode": "local" } }.

Proposed Fix

Apply stripNulls to each source before the recursive merge, so null-defaulted schema
options don't shadow baseConfig values:

# Before:
mergedConfig0 = stripNulls (
  lib.recursiveUpdate (lib.recursiveUpdate baseConfig cfg.config) inst.config
);

# After:
mergedConfig0 = stripNulls (
  lib.recursiveUpdate (lib.recursiveUpdate baseConfig (stripNulls cfg.config)) (stripNulls inst.config)
);

stripNulls is already defined in the same file, so this is a one-line change with no new helpers needed.

Workaround

Patch the generated config via jq in ExecStartPre:

jq '.gateway.mode = "local"' ~/.openclaw/openclaw.json > /tmp/openclaw-patched.json

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions