Skip to content

[Bug]: doctor --fix resolves ${...} env var references to plaintext in config file #4654

Description

@anotb

Description

Running openclaw doctor --fix resolves all ${...} environment variable references in openclaw.json to their plaintext values, writing secrets (API keys, bot tokens, passwords) directly into the config file.

Steps to Reproduce

  1. Have openclaw.json with env var references like:
    "token": "${DISCORD_BOT_TOKEN}"
  2. Have the corresponding values in ~/.openclaw/.env
  3. Run openclaw doctor --fix
  4. Observe that openclaw.json now contains:
    "token": "MTQ2NDIz..."

All ${...} references in env.vars, channel tokens, gateway password, hooks token, and any other field using env substitution are resolved to plaintext.

Expected Behavior

doctor --fix should preserve ${...} references when writing the config back. Env var substitution should only happen in memory at runtime, never persisted to disk.

Root Cause

In src/commands/doctor-config-flow.ts (line ~187), doctor loads the config via readConfigFileSnapshot() and uses snapshot.config (the fully resolved version) rather than snapshot.parsed (which preserves ${...} references). When writeConfigFile() serializes cfg back to JSON, it writes the resolved values.

The resolution happens in src/config/io.ts:386 via resolveConfigEnvVars(), and there's no mechanism to restore the original ${...} syntax on write-back.

Key files:

  • src/config/io.ts — resolves env vars during read (line 386), writes resolved config (line 478)
  • src/config/env-substitution.ts — performs substitution (lines 46-100)
  • src/commands/doctor-config-flow.ts — uses snapshot.config instead of snapshot.parsed (line 187)

Suggested Fix

Option A: Have doctor work with snapshot.parsed (unresolved) and only resolve for validation, not for write-back.

Option B: Track which config values contained ${...} references and restore them before writing.

Related

#3261 describes the same underlying problem through the gateway restart code path. This issue covers the doctor --fix path specifically, but the root cause is shared: writeConfigFile() has no concept of preserving env var references.

Environment

  • Version: 2026.1.29
  • Platform: macOS (Darwin)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions