Admin Operations

Focus Deep-Dive

Config mutation, scoped resets, and system-state commands

Admin operations are the privileged commands that modify OpenClaw’s persistent configuration, credentials, and installed services. Every mutation — from changing a model default to a full factory reset — flows through the mutateConfigFile transactional pipeline, which guarantees atomic reads, draft-based edits, and hash-checked writes so that concurrent CLI sessions never silently overwrite each other’s changes.

📚 What is this & why does it matter?

Admin operations form the control plane of OpenClaw. While regular slash commands read state and produce output, admin commands write state — they change the config file on disk, stop or start system services, and tear down workspace artifacts. Understanding this module is essential for debugging “config not saved” issues, building new administrative commands, or auditing what reset --scope full actually deletes.

💡 Real-world analogy

Think of a bank vault with a double-entry ledger. Every teller (CLI session) that wants to modify account balances must first read the current ledger page, make changes on a draft copy, and then compare a checksum before committing. If another teller changed the ledger in the meantime, the commit is rejected and the teller must re-read. mutateConfigFile is that ledger protocol — it prevents lost updates even when multiple terminals run configure simultaneously.

Contents

Overview

Primary role

Provide transactional config mutation and scoped teardown commands that safely modify OpenClaw system state.

Main inputs

RuntimeEnv context, user-supplied options (ResetOptions, UninstallOptions), and the current OpenClawConfig on disk.

Key decisions

Which reset scope to apply? Which uninstall targets to remove? Should the operation run in dry-run mode? Is a hash conflict detected?

Outputs / side effects

ConfigReplaceResult with the new config hash, service stop/start signals, and filesystem deletions for reset and uninstall scopes.

Core Types

Admin operations revolve around four key type families: the config mutation generics, the reset scope enum, the uninstall scope set, and the agent config parameters.

// src/commands/config-io.ts — config mutation signature
export async function mutateConfigFile<T = void>(params: {
  base?: ConfigMutationBase;
  baseHash?: string;
  writeOptions?: ConfigWriteOptions;
  mutate: (
    draft: OpenClawConfig,
    context: { snapshot: ConfigFileSnapshot; previousHash: string | null }
  ) => Promise<T | void> | T | void;
}): Promise<ConfigReplaceResult & { result: T | undefined }>
// src/commands/reset.ts — reset scope types
export type ResetScope = "config" | "config+creds+sessions" | "full";

export type ResetOptions = {
  scope?: ResetScope;
  yes?: boolean;
  nonInteractive?: boolean;
  dryRun?: boolean;
};
TypeSource filePurpose
ConfigReplaceResultconfig-io.tsReturn value after a successful config write — includes the new file hash and whether the write was a no-op
ResetScopereset.tsDiscriminated string union controlling how much state is wiped: config only, config + credentials + sessions, or full factory reset
UninstallScopeuninstall.tsSet of removal targets: "service", "state", "workspace", "app"
UninstallOptionsuninstall.tsCLI flags mapped to scope booleans, plus all, yes, nonInteractive, and dryRun
ConfigMutationBaseconfig-io.tsOptional base config to mutate from (defaults to reading from disk)
ConfigFileSnapshotconfig-io.tsImmutable snapshot of the config file at read time, used for conflict detection

Decision Points

DecisionWhat controls itWhy it matters
Hash conflict detectionbaseHash parameter vs. current file hash at write timePrevents concurrent CLI sessions from silently overwriting each other’s config changes
Reset scope selectionResetScope enum passed via --scope flag or interactive promptDetermines whether only config, config + credentials + sessions, or everything is wiped
Uninstall scope setbuildScopeSelection(opts) reads boolean flags and --allAllows granular teardown — stop service only, remove state only, or nuke everything
Dry-run guarddryRun: boolean on both ResetOptions and UninstallOptionsLets operators preview destructive actions without executing them
Interactive confirmationyes flag bypasses prompt; nonInteractive auto-declinesSafety net for destructive operations in interactive terminals; CI pipelines use --yes
Agent config merge strategyapplyAgentConfig parameter presence checksOnly fields explicitly provided are overwritten; omitted fields preserve existing values

How It Works

Admin operations follow three distinct patterns depending on the command: transactional config mutation for configure and agent config, scoped reset for the reset command, and multi-scope uninstall for teardown. All three patterns share the same dry-run and confirmation guardrails.

Config Mutation Lifecycle

The mutateConfigFile function implements an optimistic-concurrency protocol. It reads the current config file into a snapshot, passes a mutable draft to the caller’s mutate callback, and then performs a compare-and-swap write using the snapshot’s hash. If another process modified the file between read and write, the operation fails with a hash-conflict error rather than silently overwriting.

flowchart TD
  START["mutateConfigFile() called"]
  READ["Read config file from disk"]
  SNAP["Create ConfigFileSnapshot + hash"]
  DRAFT["Clone snapshot into mutable draft"]
  MUTATE["Execute caller's mutate(draft, context)"]
  COMPARE{"baseHash === currentHash?"}
  WRITE["Atomically write draft to disk"]
  RESULT["Return ConfigReplaceResult + result T"]
  CONFLICT["Throw hash-conflict error"]
  DRYRUN{"dryRun enabled?"}
  PREVIEW["Return preview without writing"]

  START --> READ
  READ --> SNAP
  SNAP --> DRAFT
  DRAFT --> MUTATE
  MUTATE --> DRYRUN
  DRYRUN -->|Yes| PREVIEW
  DRYRUN -->|No| COMPARE
  COMPARE -->|Match| WRITE
  COMPARE -->|Mismatch| CONFLICT
  WRITE --> RESULT

The flowchart above shows the full config mutation pipeline. The critical path runs from top to bottom: read → snapshot → draft → mutate → compare → write. The dry-run branch exits early after mutation without touching disk, and the hash-conflict branch raises an error that the caller can retry.

Configure Command

The configureCommand and configureCommandWithSections functions launch an interactive wizard that collects user input, then calls mutateConfigFile internally to persist changes. The sections parameter allows callers to restrict the wizard to specific config areas (e.g., only model settings).

// src/commands/configure.commands.ts — configure entry points
export async function configureCommand(
  runtime: RuntimeEnv = defaultRuntime
) {
  await runConfigureWizard({ command: "configure" }, runtime);
}

export async function configureCommandWithSections(
  sections: WizardSection[],
  runtime: RuntimeEnv = defaultRuntime
) {
  await runConfigureWizard(
    { command: "configure", sections },
    runtime
  );
}

Scoped Reset Flow

The resetCommand applies a progressively wider teardown depending on the chosen ResetScope. The "config" scope only removes the config file. The "config+creds+sessions" scope additionally clears stored credentials and active session tokens. The "full" scope performs a factory reset that also removes workspace artifacts, agent directories, and cached data.

sequenceDiagram
    participant User
    participant CLI as resetCommand()
    participant Confirm as Confirmation Prompt
    participant Config as Config File
    participant Creds as Credentials Store
    participant Sessions as Session Store
    participant Workspace as Workspace Artifacts

    User->>CLI: reset --scope config+creds+sessions
    CLI->>Confirm: "This will delete config, credentials, and sessions. Continue?"
    Confirm-->>CLI: yes
    CLI->>Config: Delete config file
    Config-->>CLI: OK
    CLI->>Creds: Clear stored credentials
    Creds-->>CLI: OK
    CLI->>Sessions: Invalidate active sessions
    Sessions-->>CLI: OK
    CLI-->>User: Reset complete (config + creds + sessions)

The sequence diagram shows the reset flow for the "config+creds+sessions" scope. Each store is cleared in order: config first, then credentials, then sessions. If --yes is passed, the confirmation prompt is skipped. If --dry-run is set, the command prints what it would delete without actually deleting anything.

// src/commands/reset.ts — reset command signature
export type ResetScope = "config" | "config+creds+sessions" | "full";

export type ResetOptions = {
  scope?: ResetScope;
  yes?: boolean;
  nonInteractive?: boolean;
  dryRun?: boolean;
};

export async function resetCommand(
  runtime: RuntimeEnv,
  opts: ResetOptions
)

Multi-Scope Uninstall

The uninstall command uses a scope-set pattern rather than a single enum. The buildScopeSelection helper reads individual boolean flags (--service, --state, --workspace, --app) and the --all shorthand to produce a Set<UninstallScope>. Each scope triggers a specific teardown action, and scopes execute in dependency order: service is stopped before state is removed, and workspace is cleaned before app binaries are deleted.

// src/commands/uninstall.ts — scope selection and teardown
type UninstallScope = "service" | "state" | "workspace" | "app";

export type UninstallOptions = {
  service?: boolean;
  state?: boolean;
  workspace?: boolean;
  app?: boolean;
  all?: boolean;
  yes?: boolean;
  nonInteractive?: boolean;
  dryRun?: boolean;
};

function buildScopeSelection(
  opts: UninstallOptions
): { scopes: Set<UninstallScope>; hadExplicit: boolean; }

async function stopAndUninstallService(
  runtime: RuntimeEnv
): Promise<boolean>
⚠ Dependency ordering matters: The uninstall pipeline always processes scopes in the order service → state → workspace → app. Attempting to remove state while the service is still running would cause file-lock errors on Windows. The stopAndUninstallService helper handles graceful shutdown before any filesystem deletions begin.

Code Walkthrough — applyAgentConfig

This walkthrough traces how agent configuration parameters are merged into the global OpenClawConfig object. The function uses a selective-merge strategy: only fields that are explicitly provided in the params object are overwritten; omitted fields preserve their existing values.

Receive the current config and parameters

The function takes the full OpenClawConfig as its first argument and a params object with the target agentId plus optional override fields. The config is treated as immutable input — a new config object is returned.

// src/commands/agents.config.ts — entry point
export function applyAgentConfig(
  cfg: OpenClawConfig,
  params: {
    agentId: string;
    name?: string;
    workspace?: string;
    agentDir?: string;
    model?: string;
  }
): OpenClawConfig

Look up or create the agent entry

The function searches cfg.agents for an entry matching params.agentId. If no entry exists, a new agent record is initialized with default values. This ensures that applyAgentConfig can be called for both existing agents and first-time setup.

Selective field merge

Each optional parameter is checked for presence (not just truthiness — an empty string is a valid override). Only present fields are written to the agent record. This prevents accidental field clearing when only one property needs to change.

Return new config

A new OpenClawConfig object is returned with the updated agents array. The original cfg is never mutated, keeping the function pure and compatible with the mutateConfigFile draft pattern where the caller controls when the draft is committed.

Setup Command

The setupCommand orchestrates first-run initialization. It creates workspace directories, writes a default config file (via mutateConfigFile), and optionally launches the configure wizard. It is safe to call on an already-initialized workspace — existing files are not overwritten.

// src/commands/setup.ts — setup entry point
export async function setupCommand(
  opts?: { workspace?: string },
  runtime: RuntimeEnv = defaultRuntime
)
✓ Idempotent design: setupCommand checks for the existence of each artifact before creating it. Running setup on an already-configured workspace is a safe no-op — it will only create missing files and directories.

Edge Cases

ScenarioBehaviorRationale
Hash conflict during mutateConfigFileThrows a hash-conflict error; caller must re-read and retryPrevents concurrent CLI sessions from silently overwriting each other’s changes
Config file missing on resetSkips config deletion, proceeds to next scopeA missing config is already in the desired end-state; no error is warranted
--dry-run with --scope fullPrints every file and directory that would be deleted, returns without side effectsOperators need a way to audit the blast radius of a full reset before committing
nonInteractive: true without yes: trueAutomatically declines destructive operationsCI pipelines that accidentally invoke reset without --yes should fail safely
applyAgentConfig with unknown agentIdCreates a new agent entry with defaults, then applies overridesSupports first-time agent setup without requiring a separate “create agent” step
Service still running during uninstallstopAndUninstallService sends graceful shutdown; waits up to 10s before force-killingPrevents file-lock errors when removing service state on Windows
buildScopeSelection with no flags and no --allReturns empty set with hadExplicit: false; caller prompts interactivelyPrevents accidental uninstall when user forgets to specify what to remove
Concurrent setup callsSecond call is a no-op for already-created artifactsIdempotent design ensures race conditions don’t produce duplicate files

Configuration

SettingLocationDefaultDescription
Config file pathRuntimeEnv.configPath~/.openclaw/config.jsonLocation of the OpenClaw config file on disk; all mutations target this path
Reset scopeResetOptions.scope"config"Controls which stores are wiped: config only, config + creds + sessions, or full
Dry-run mode--dry-run flagfalseWhen enabled, destructive commands print what they would do without executing
Auto-confirm--yes flagfalseBypasses interactive confirmation prompts for destructive operations
Non-interactive mode--non-interactive flagfalseWhen set, automatically declines any confirmation prompt (safe default for CI)
Workspace directorysetupCommand opts.workspaceprocess.cwd()Root directory for workspace initialization
Agent directoryapplyAgentConfig params.agentDirAgent-specific defaultOverride the filesystem path for a specific agent’s working directory
Permission Gating

Authorization checks that run before any admin operation is allowed to execute.

Command Parsing

How raw user input is parsed into structured command objects before reaching admin handlers.

Command Registry

Where admin commands are registered and looked up during dispatch.

Source Files

Key files involved in admin operations
src/
└── commands/
    ├── config-io.ts              ← mutateConfigFile() transactional mutation
    ├── configure.commands.ts     ← configureCommand(), wizard launcher
    ├── reset.ts                  ← resetCommand(), ResetScope, ResetOptions
    ├── uninstall.ts              ← uninstallCommand(), buildScopeSelection()
    ├── agents.config.ts          ← applyAgentConfig() agent config merge
    └── setup.ts                  ← setupCommand() first-run initialization