libnyanpasu/clash-nyanpasu · info

[mutation-degradation]

Error message

[mutation-degradation]

What it means

The MutationDegradationNotifier component in the root route observes mutation-degradation events emitted by the state/config layer — cases where a mutation was applied but some post-commit side effect failed and the system degraded gracefully. For each degradation it logs a console.warn with phase, code, retryable, and message; the backend's message field is diagnostic-only, the primary copy is phase + code. This is a diagnostic log, not a thrown error.

Source

Thrown at frontend/nyanpasu/src/pages/__root.tsx:188

  return m.mutation_degraded_item({
    phase: localizeDegradationPhase(degradation.phase),
    detail: localizeDegradationCode(degradation.code),
  })
}

function MutationDegradationNotifier() {
  useEffect(
    () =>
      // setMutationDegradationHandler returns a disposer; useEffect cleanup
      // passes it through so StrictMode remount / HMR leave no dangling handler.
      setMutationDegradationHandler((degradations) => {
        if (degradations.length === 0) {
          return
        }

        // Backend `message` is diagnostic-only; primary copy is phase + code.
        for (const degradation of degradations) {
          console.warn('[mutation-degradation]', {
            phase: degradation.phase,
            code: degradation.code,
            retryable: degradation.retryable,
            message: degradation.message,
          })
        }

        const items = degradations.map(formatDegradationItem).join('; ')
        message(m.mutation_degraded_summary({ items }), {
          title: m.mutation_degraded_title(),
          kind: 'warning',
        }).catch((error) => {
          console.error('[mutation-degradation] failed to show warning', error)
        })
      }),
    [],
  )
  return null

View on GitHub (pinned to f7dbce2997)

Solutions

  1. Read the logged `phase` and `code` fields — they identify which post-commit side effect degraded and why.
  2. If `retryable` is true, retry the mutation or trigger the affected action (e.g. restart core) — transient side-effect failures often succeed on retry.
  3. If not retryable, fix the underlying condition indicated by the code (e.g. invalid runtime config, missing core binary) and re-apply the change.
  4. Use the `message` field only as diagnostic detail; rely on phase + code for user-facing copy and triage.

Example fix

// before: ignoring degradations silently
await client.patch_app_config(patch);
// after: check result and handle degraded side effects
const result = await client.patch_app_config(patch);
if (result.degradations.some(d => d.retryable)) {
  await client.restart_core().catch(reportDegraded);
}
Defensive patterns

Strategy: retry

Try / catch

try {
  await client.patch_app_config(patch);
} catch (e) {
  if (e.degradations?.some(d => d.retryable)) {
    await client.restart_core(); // retry the degraded side effect
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: A config/profile patch mutation committed but a post-commit side effect (e.g. core restart, runtime config regeneration, tray/UI notification) failed; the backend reports a degradation entry with a phase, a code, and a retryable flag, and the notifier logs it.

Common situations: Core process fails to restart after a config change; a side effect times out after a config patch; transient IPC errors while saving profile changes. Users see degraded behavior (stale runtime config) while persisted state is fine.

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08). Data as JSON: /api/errors/3e1eca013ac47456. Report an issue: GitHub.