How Clash Nyanpasu Generates and Patches Runtime YAML Configuration

Clash Nyanpasu maintains an in-memory runtime configuration object that serializes to YAML for display and updates atomically through a type-safe patching mechanism when users modify settings like allow-lan or mode.

Clash Nyanpasu manages proxy configurations through a dynamic runtime YAML configuration that bridges the React-based frontend and the Clash core. This article examines how the Tauri-based backend generates readable YAML from internal state and applies partial updates through a structured patching pipeline, based on the actual implementation in the libnyanpasu/clash-nyanpasu repository.

Architecture of Runtime YAML Configuration Generation

The generation flow converts the internal Mapping representation into a human-readable YAML string that reflects the currently active configuration (base config plus any merge files or scripts).

The Runtime Data Structure

The runtime state lives in the IRuntime struct defined in backend/tauri/src/config/runtime.rs. This structure holds the active configuration as an Option<Mapping> and provides thread-safe access through a singleton pattern.

// backend/tauri/src/config/runtime.rs (lines 18-26)
pub struct IRuntime {
    pub config: Option<Mapping>,  // The actual YAML mapping
}

impl IRuntime {
    pub fn latest() -> &'static IRuntime {
        // Returns the singleton instance
    }
}

Serializing to YAML via Tauri Commands

When the frontend requests the runtime configuration, the get_runtime_yaml command in backend/tauri/src/ipc.rs (lines 400-410) handles the serialization:

#[tauri::command]
pub fn get_runtime_yaml() -> Result<String> {
    let runtime = Config::runtime().latest();
    let yaml = runtime
        .config
        .as_ref()
        .ok_or_else(|| anyhow!("no runtime config"))
        .and_then(|c| serde_yaml::to_string(c).context("failed to convert to yaml"))?;
    Ok(yaml)
}

The resulting YAML string represents the merged configuration—the base profile combined with any user-defined merge files and scripts that Clash is actively using.

Patching Runtime Configuration in Clash Nyanpasu

The patching mechanism allows the UI to modify specific fields (such as allow-lan, ipv6, log-level, or mode) without replacing the entire configuration file.

The Patch Payload Structure

To ensure type safety and prevent accidental modification of read-only keys, Clash Nyanpasu defines a PatchRuntimeConfig struct that only includes editable fields:

// backend/tauri/src/config/runtime.rs (lines 6-16)
#[derive(Default, Debug, Clone, Deserialize, Serialize, specta::Type)]
pub struct PatchRuntimeConfig {
    #[serde(default)] pub allow_lan: Option<bool>,
    #[serde(default)] pub ipv6:      Option<bool>,
    #[serde(default)] pub log_level: Option<String>,
    #[serde(default)] pub mode:      Option<String>,
}

The Patching Pipeline

When a user toggles a setting in the UI, the following pipeline executes:

  1. Frontend Invocation: The TypeScript service calls patch_clash_config via Tauri's invoke API.

  2. Command Deserialization: In backend/tauri/src/ipc.rs (lines 444-456), the payload deserializes into a serde_yaml::Mapping and validates that it represents a YAML mapping structure.

  3. Feature Orchestration: The feat::patch_clash function in backend/tauri/src/feat.rs (lines 12-78) coordinates the update:

    • Updates the draft Clash configuration via Config::clash().draft().patch_config()
    • Applies changes to the runtime via Config::runtime().latest().patch_config(patch)
    • Validates critical fields (mixed-port, external-controller)
    • Optionally restarts the Clash core to apply changes
    • Persists the updated configuration to disk
  4. Runtime Application: The patch_config method in backend/tauri/src/config/runtime.rs (lines 32-58) iterates through the PatchRuntimeConfig fields and inserts or overwrites the corresponding keys in the stored Mapping.

After patching completes, the runtime Mapping serves as the source for all future get_runtime_yaml calls, ensuring consistency between the active Clash process and the displayed configuration.

Code Examples for Runtime Configuration Management

Reading Runtime YAML in TypeScript

The frontend retrieves the current configuration through the Tauri service layer:

// frontend/interface/src/service/tauri.ts (line 225)
import { invoke } from '@tauri-apps/api/tauri';

export async function getRuntimeYaml(): Promise<string> {
  // Returns a YAML string like "# Clash configuration\nmixed-port: 7890\n..."

  return await invoke<string>('get_runtime_yaml');
}

Patching Configuration in TypeScript

To modify runtime settings, construct a PatchRuntimeConfig object and invoke the patch command:

// frontend/interface/src/service/tauri.ts (line 28)
import { invoke } from '@tauri-apps/api/tauri';

export interface PatchRuntimeConfig {
  allow_lan?: boolean;
  ipv6?: boolean;
  log_level?: string;
  mode?: string;
}

// Example: enable LAN and switch mode to "Rule"
await invoke<void>('patch_clash_config', {
  payload: { allow_lan: true, mode: 'Rule' } as PatchRuntimeConfig,
});

Rust Implementation Details

The backend command implementations demonstrate how the runtime state transitions between Rust data structures and YAML strings:

// backend/tauri/src/ipc.rs – Runtime generation
#[tauri::command]
pub fn get_runtime_yaml() -> Result<String> {
    let runtime = Config::runtime().latest();
    let yaml = runtime
        .config
        .as_ref()
        .ok_or_else(|| anyhow!("no runtime config"))
        .and_then(|c| serde_yaml::to_string(c).context("failed to convert to yaml"))?;
    Ok(yaml)
}

// backend/tauri/src/ipc.rs – Configuration patching
#[tauri::command]
pub async fn patch_clash_config(payload: PatchRuntimeConfig) -> Result {
    let mapping = serde_yaml::to_value(&payload)?
        .as_mapping()
        .cloned()
        .ok_or_else(|| IpcError::Custom("Expected a mapping".into()))?;
    
    feat::patch_clash(mapping).await
}

Key Source Files in Clash Nyanpasu

Role File Description
Tauri command – read backend/tauri/src/ipc.rs (lines 400‑410) Implements get_runtime_yaml.
Tauri command – write backend/tauri/src/ipc.rs (lines 444‑456) Implements patch_clash_config.
Runtime data structure backend/tauri/src/config/runtime.rs (lines 18‑26, 32‑58) Holds the in‑memory YAML mapping and the patch_config logic.
Patch orchestration backend/tauri/src/feat.rs (lines 12‑78) Calls Config::runtime().latest().patch_config, restarts core, persists changes.
Frontend service – get frontend/interface/src/service/tauri.ts (line 225) Calls the Tauri get_runtime_yaml command.
Frontend service – patch frontend/interface/src/service/tauri.ts (line 28) Calls the Tauri patch_clash_config command.
TypeScript bindings frontend/interface/src/ipc/bindings.ts Defines the PatchRuntimeConfig type used by the UI.

Summary

  • Runtime YAML generation in Clash Nyanpasu converts the in-memory Mapping stored in IRuntime to a YAML string via serde_yaml::to_string, exposed through the get_runtime_yaml Tauri command.

  • Configuration patching uses a type-safe PatchRuntimeConfig struct to restrict editable fields to allow_lan, ipv6, log_level, and mode, preventing accidental corruption of read-only keys.

  • The patching pipeline flows from TypeScript UI calls through ipc.rs deserialization, into feat.rs for orchestration (draft updates, validation, core restart), and finally applies changes via runtime.rs which updates the stored Mapping.

  • All operations are atomic and thread-safe, utilizing Rust's singleton pattern for the runtime configuration to ensure consistency between the active Clash process and the UI display.

Frequently Asked Questions

What is the runtime YAML configuration in Clash Nyanpasu?

The runtime YAML configuration is the active, in-memory representation of the Clash proxy settings currently being used by the core process. It lives in the IRuntime struct as a serde_yaml::Mapping and reflects the merged state of the base profile, merge files, and any applied scripts, distinct from the static configuration files stored on disk.

How does Clash Nyanpasu handle partial configuration updates?

Clash Nyanpasu handles partial updates through the PatchRuntimeConfig struct, which only exposes four editable fields: allow_lan, ipv6, log_level, and mode. When the UI sends a patch, the backend deserializes it into a YAML mapping, applies it to both the draft Clash config and the runtime mapping via Config::runtime().latest().patch_config(), validates critical fields like mixed-port, and optionally restarts the core to apply changes.

Where is the runtime configuration stored in the codebase?

The runtime configuration is primarily stored and managed in backend/tauri/src/config/runtime.rs, which defines the IRuntime struct holding the Option<Mapping> config field. The generation command resides in backend/tauri/src/ipc.rs (lines 400-410), while the patching orchestration logic is implemented in backend/tauri/src/feat.rs (lines 12-78). Frontend bindings exist in frontend/interface/src/service/tauri.ts.

Can I manually edit the runtime YAML configuration?

While you can read the runtime YAML via the get_runtime_yaml command for inspection or export, manual editing is not supported through the public API. The patch_clash_config command requires a strongly-typed PatchRuntimeConfig payload that only accepts specific fields (allow_lan, ipv6, log_level, mode), ensuring type safety and preventing corruption of the runtime state. Direct manipulation of the underlying Mapping would bypass validation logic and could destabilize the Clash core.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →