> ## Documentation Index
> Fetch the complete documentation index at: https://ccsafetynet.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Configuration recovery: ready and degraded state

> How CC Safety Net behaves when configuration cannot be verified: ready versus degraded state, what stays enforced, how the state is reported, and the exact commands that repair it.

CC Safety Net loads a policy snapshot on every tool call, reading your local policy file, `rule.json`, rule lockfiles, and digest-verified rulebook caches. That load never writes, never reaches the network, and never caches results, so the snapshot always reflects the configuration currently on disk.

The snapshot has exactly two states: **ready** and **degraded**. This page is the complete contract for both, including what stops being enforced when a source is rejected and how to get back to `ready`.

## Ready versus degraded

| State      | When it happens                                              | What it means                                                                                                              |
| ---------- | ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
| `ready`    | Every active source loaded and validated cleanly             | Ordinary evaluation against exactly the configuration you wrote                                                            |
| `degraded` | Any rule error, any rule warning, or any `policy.json` error | Ordinary evaluation continues against a fallback, and every reporting surface carries a warning naming the rejected source |

A single warning is enough to move the runtime to `degraded`. The distinction between an error and a warning is about the source, not about severity of the state:

* An **error** names a source that was **dropped**. That source contributes no rules at all.
* A **warning** names a source that **stays active** with only the rejected part ignored.

Both produce `degraded`.

<Note>
  `ready` and `degraded` are the only verdicts `cc-safety-net status` prints — the verdict is read straight from the snapshot state. A disabled Claude Code plugin does not change the verdict; `status` reports it as the first item under `Not active`: "plugin cc-safety-net\@cc-marketplace is disabled in Claude Code; nothing is enforced in Claude Code until it is re-enabled. Other integrations are not affected."
</Note>

## Invalid configuration does not block your work

Invalid configuration never denies ordinary work merely because it is invalid. An invalid candidate is never enforced, but it never locks the agent out either.

* A rule source that cannot be verified is **dropped**. Its rules stop being enforced.
* Every other verified scope keeps enforcing its rules.
* Every built-in protection keeps applying in every case — destructive-command rules, secret protection, policy-file protection, and Git-metadata protection read no rule configuration at all.
* An unreadable `policy.json` falls back to **protective** defaults, so both destructive-command protection and secret protection stay on.

There is no special recovery mode and no allowlist while degraded, because nothing is denied for being unconfigurable. Reading `rule.json`, editing it in place, and running `cc-safety-net rule sync` are ordinary tool calls that pass or fail on their own merits, so your agent can repair the configuration itself.

<Warning>
  Dropping a source is not security-neutral. It **removes** the denials that source contributed, so a command you deliberately blocked in a dropped rulebook can run again until you repair and re-sync it. Never assume a rulebook named in an error is still protecting you.
</Warning>

The one thing that stays protected in every state is the canonical user `policy.json`. Policy-file protection and Git-metadata protection run **before** the configuration snapshot is loaded, so they cannot be affected by a broken config. See [Policy](/docs/configuration/policy) for exactly which operations are blocked.

## Failure-to-fallback matrix

### Errors — the source is dropped

| Failure                                                                                                                             | What stops being enforced                                                  | What still applies                                 |
| ----------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | -------------------------------------------------- |
| Missing lockfile while rule sources are configured                                                                                  | Every rulebook in that scope                                               | The other scope's verified rules and all built-ins |
| Missing lock entry for a configured source                                                                                          | That one rulebook                                                          | Every other rulebook and all built-ins             |
| Missing cache entry for a source                                                                                                    | That one rulebook                                                          | Every other rulebook and all built-ins             |
| Cache digest mismatch                                                                                                               | That one rulebook                                                          | Every other rulebook and all built-ins             |
| Cached rulebook is unparseable or fails the schema                                                                                  | That one rulebook                                                          | Every other rulebook and all built-ins             |
| A lockfile entry does not match its configured source identity — wrong `kind`, or a `path`/`name` that differs from the source spec | Every rulebook in that scope — the whole lockfile is rejected as malformed | The other scope's verified rules and all built-ins |
| `rule.json` is malformed, empty, or has an unsupported `version`                                                                    | That whole scope, **including its `transparent_wrappers`**                 | The other scope's verified rules and all built-ins |
| The policy filesystem cannot be read safely                                                                                         | That scope                                                                 | The other scope's verified rules and all built-ins |

Every one of these messages names the file or source it rejected and, where a repair exists, tells you to run `cc-safety-net rule sync`.

### Warnings — the source stays active

| Failure                                       | What is ignored                                | What stays enforced                                            |
| --------------------------------------------- | ---------------------------------------------- | -------------------------------------------------------------- |
| Two active rulebooks claim the same name      | The later rulebook, whose rules are not active | The first claim, resolved user scope first                     |
| Unknown override key in `rule.json`           | Only that one override                         | Every other override and every rule keeps its configured state |
| A project override targets a user-scoped rule | Only that one override                         | The rule keeps its user-configured state                       |

<Warning>
  The local rulebook source is deliberately absent from both tables. Runtime load reads only the digest-verified cache, so editing a local rulebook, breaking it, or deleting its source directory produces **no warning at all** — the cached rulebook, as it was at the last successful sync, is what enforces, not the file you just edited. Your edit becomes active only when `cc-safety-net rule sync` succeeds.
</Warning>

Duplicate rulebook names resolve deterministically: the first claim wins, and the user scope is loaded first, so a name claimed by your user scope shadows the project one. The later rulebook contributes nothing rather than partially shadowing rules. Because this is resolved rather than fatal, `rule sync` for one scope still succeeds when the other scope already uses that name.

### `policy.json` — salvaged or replaced with protective defaults

| Situation                                          | Result                                                                                                            | State                   |
| -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | ----------------------- |
| The file does not exist                            | Built-in defaults                                                                                                 | `ready` — no diagnostic |
| The file is empty or whitespace only               | Built-in protective defaults                                                                                      | `degraded`              |
| The file is not valid JSON                         | Built-in protective defaults                                                                                      | `degraded`              |
| The file parses to something that is not an object | Built-in protective defaults                                                                                      | `degraded`              |
| The file parses to an object but fails validation  | **Field-by-field salvage**: every recognized valid section stays active, and protective defaults replace the rest | `degraded`              |
| The file is valid                                  | Your policy, exactly as written                                                                                   | `ready`                 |

Field-level salvage means one bad field cannot drop the protections the rest of the file still configures. The protective defaults deliberately err toward more denials: destructive-command protection and secret protection are forced on, allow paths are dropped, and disabling overrides are dropped. See [Policy](/docs/configuration/policy) for the per-field salvage behavior.

The runtime never rewrites `policy.json`. Repair it by hand, or use the repair action in the dashboard. Note that while the file has errors, the dashboard form shows the full defaults rather than the salvaged values, and saving is refused until you repair — the repair action itself preserves every recognized valid setting, replacing the whole file with defaults only when the JSON cannot be parsed.

## Transparent wrappers are the one coverage gap

`transparent_wrappers` is declared in `rule.json`, not in a rulebook, and `rule.json` carries no lock or digest. That has two consequences:

* A **dropped rulebook** keeps its scope's wrappers, because `rule.json` itself is still readable.
* An **unreadable `rule.json`** loses that scope's wrappers, because there is no verified copy to fall back to. Analysis stops looking through those wrapper commands to the protected command underneath.

This is the one place where rejected configuration reduces built-in coverage rather than only removing your own rules. Repair `rule.json` first when a scope is dropped for that reason.

## Where "fail closed" still applies

"Fail closed" is accurate for runtime and analysis failures, and it denies **that one tool call** — it is never a description of what invalid configuration does.

| Case                                                                   | Behavior                                                                            |
| ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| The analyzer or a dependency throws unexpectedly at any guard stage    | The tool call is denied with a "failed closed" reason, in every mode                |
| Malformed or oversized hook or tool payload                            | Denied, in every mode                                                               |
| An empty or whitespace-only command on the command route               | Denied, in every mode                                                               |
| A command exceeds the recursion depth limit                            | Denied, in every mode                                                               |
| A command structure exceeds safe validation limits                     | Denied, in every mode                                                               |
| A command cannot be tokenized while the `fail_closed` capability is on | Denied — see [strict mode](/docs/configuration/modes#strict-mode-cc_safety_net_strict=1) |
| Worktree relaxation cannot positively confirm a linked worktree        | The relaxation is not applied and the stricter default stays                        |

Invalid configuration is the opposite: rule sources are dropped, `policy.json` is salvaged or replaced with protective defaults, and work continues.

## How degraded state is reported

| Surface                      | What you see                                                                                                                       |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| The next user-visible denial | A `Config warning:` line carrying the full reason, appended to the block message                                                   |
| Audit records                | A `configFallback` flag, set on allowed and denied decisions alike                                                                 |
| `cc-safety-net status`       | The verdict `ready` or `degraded`, and one line per diagnostic under `Not active`                                                  |
| `cc-safety-net doctor`       | A `config.runtime-degraded` warning finding titled "Runtime is enforcing a fallback configuration", with the full reason as detail |
| `cc-safety-net rule list`    | An `Issues` and a `Warnings` section. Rule configuration only                                                                      |
| Status line                  | A `⚠️` marker while the snapshot is degraded                                                                                       |
| Dashboard                    | The state in the protection banner                                                                                                 |

`doctor` is the only command that reports both rule configuration and `policy.json`. See [CLI commands](/docs/reference/cli-commands) for each command's options and exit behavior.

Two structural limits are worth knowing:

* The `Config warning:` line and the audit `configFallback` flag appear only on decisions made **after** the snapshot loads. Policy-file and Git-metadata denials happen before that, so they carry neither.
* Diagnostics **name** the rejected file and the condition; they never copy its bytes. A secret that happens to sit inside a malformed config file is not reproduced in the message.

## What you will and will not notice

* **A dropped rule source is quiet.** It removes denials rather than adding them, so a clean session produces no friction and no signal at all. This is the case to check deliberately after any change to rule configuration and after every upgrade. Make `cc-safety-net status` a habit; run `doctor` for the full report.
* **A local rulebook edit and an unmigrated legacy config are quieter still.** Neither produces any runtime diagnostic: the digest-verified cache keeps enforcing the pre-edit rulebook, and a legacy file is simply not loaded. `rule sync` promotes the edit; `rule verify` flags the legacy file.
* **An invalid `policy.json` mostly announces itself**, because rejected sections fall back to protective defaults: both protections forced on, allow paths dropped, disabling overrides dropped. You discover it as *more* denials than you configured.
* **The quiet half of an invalid `policy.json`**: an invalid `safety.level` silently falls back to `standard`, so a typo in `paranoid` **lowers** your preset. Invalid `secret_protection.deny_paths` entries and per-rule overrides that would raise a rule above its default are discarded rather than repaired.
* Only the status line marker is passive. The `Config warning:` line needs an unrelated denial to appear on, and every other surface waits for you to run a command or open the dashboard.

## Recovering

Every command below is an ordinary tool call, so your agent can run the whole sequence itself while the runtime is degraded. [CLI commands](/docs/reference/cli-commands) has the full options and exit behavior for each one.

<Steps>
  <Step title="Check the verdict">
    ```bash theme={"dark"}
    npx cc-safety-net status
    ```

    Prints `ready` or `degraded`, plus one line per diagnostic under `Not active`. A disabled Claude Code plugin appears as the first `Not active` item, not as a separate verdict. It is purely informational, so use it as the cheap habitual check rather than a gate.
  </Step>

  <Step title="Get the full report">
    ```bash theme={"dark"}
    npx cc-safety-net doctor
    ```

    The one command that covers both rule configuration and `policy.json`. A degraded runtime appears as the `config.runtime-degraded` warning, and the finding's detail is the full reason naming every rejected source.
  </Step>

  <Step title="See what is actually active">
    ```bash theme={"dark"}
    npx cc-safety-net rule list
    ```

    Lists what is actually active, followed by `Issues` and `Warnings`. Use it to confirm which rules a dropped source took with it. It exits non-zero only when the policy has errors; warnings alone print under `Warnings` and exit `0`.
  </Step>

  <Step title="Validate your rule configuration">
    ```bash theme={"dark"}
    npx cc-safety-net rule verify
    ```

    Validates the user and project `rule.json` against the schema and replays the runtime load, so it catches the same problems the guard would hit. It also flags legacy files that still need migration. One write to expect: when a valid `rule.json` has no `$schema` key, `rule verify` adds one and prints `Added $schema to <scope> config.` — otherwise it changes nothing.
  </Step>

  <Step title="Repair and re-sync">
    ```bash theme={"dark"}
    npx cc-safety-net rule sync
    ```

    Rewrites the lockfile and cache for the scope you are syncing, then reloads that scope exactly the way the guard loads it. If any diagnostic remains, it reports that diagnostic and exits non-zero instead of claiming success — so `Rule config synced.` is a real all-clear for that scope.

    Verification covers only the scope being synchronized.
  </Step>

  <Step title="Fix policy.json by hand">
    The runtime never rewrites `policy.json`. Correct the fields named in the diagnostic yourself, or use the repair action in the dashboard, then rerun `status`. See [Policy](/docs/configuration/policy) for the full schema and defaults.
  </Step>
</Steps>

Repeat `status` after each repair. The runtime reloads on the next tool call, so there is nothing to restart.

## Legacy inline rules are inert until migrated

Legacy inline config files — `~/.cc-safety-net/config.json` and `.safety-net.json` — are no longer loaded at runtime, and the runtime emits no diagnostic about them: **their rules are not enforced at all** while everything else keeps working, and the snapshot stays `ready`. It is the classic silent degradation after an upgrade: nothing breaks, nothing is protected by those rules, and nothing reports it during a session. `cc-safety-net rule verify` is the surface that flags a legacy file still awaiting migration.

Migrate it:

```bash theme={"dark"}
npx -y cc-safety-net rule migrate
```

`rule migrate` propagates the sync result, so if the migrated scope still has a diagnostic it reports that instead of succeeding. The migrated files are written and the legacy file is kept, so you can fix the reported problem and run it again. See [Custom rules](/docs/configuration/custom-rules) for the rulebook layout it migrates into.

## Related pages

<CardGroup cols={2}>
  <Card title="Policy" icon="file-lock" href="/docs/configuration/policy">
    The complete `policy.json` contract, defaults, and per-field salvage.
  </Card>

  <Card title="Custom rules" icon="list-checks" href="/docs/configuration/custom-rules">
    Rulebook layout, sources, lock and cache, overrides, and transparent wrappers.
  </Card>

  <Card title="CLI commands" icon="terminal" href="/docs/reference/cli-commands">
    Full options and exit behavior for `status`, `doctor`, and every `rule` subcommand.
  </Card>

  <Card title="Audit log" icon="scroll-text" href="/docs/reference/audit-log">
    Where decisions are recorded, the entry schema, and retention.
  </Card>
</CardGroup>
