Skip to main content
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

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.
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.”

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.
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.
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 for exactly which operations are blocked.

Failure-to-fallback matrix

Errors — the source is dropped

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

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.
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

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 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. 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

doctor is the only command that reports both rule configuration and policy.json. See 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 has the full options and exit behavior for each one.
1

Check the verdict

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.
2

Get the full report

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.
3

See what is actually active

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.
4

Validate your rule configuration

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.
5

Repair and re-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.
6

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 for the full schema and defaults.
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:
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 for the rulebook layout it migrates into.

Policy

The complete policy.json contract, defaults, and per-field salvage.

Custom rules

Rulebook layout, sources, lock and cache, overrides, and transparent wrappers.

CLI commands

Full options and exit behavior for status, doctor, and every rule subcommand.

Audit log

Where decisions are recorded, the entry schema, and retention.
Last modified on August 5, 2026