rule.json in both scopes. Every rulebook a rule.json lists is a live file, read from <scope>/rules/<name>/rulebook.json on that same call. The 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.
Configuration states
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.
degraded.
ready and degraded are the only verdicts cc-safety-net status prints. It reads the verdict directly 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 behavior
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 do not read rule configuration.
- An unreadable user
policy.jsonfalls back to protective defaults, so both destructive-command protection and secret protection stay on. An unreadable projectpolicy.jsoncontributes nothing and leaves the user policy in force.
rule.json, editing it in place, and running cc-safety-net rule update are ordinary tool calls that pass or fail on their own merits, so your agent can repair the configuration itself.
What stays protected in every state is policy.json in both scopes. Policy-file protection and Git-metadata protection run before the policy snapshot is loaded, so they cannot be affected by a broken config. See Policy for exactly which operations are blocked.
Configuration fallback matrix
Errors that drop the source
Every one of these messages names the file or source it rejected and the repair that fits it. A missing rulebook for a remote source ends with
run `cc-safety-net rule update` to vendor <source>. A missing rulebook for a local source ends with create that file or remove that source from the rules config. An invalid rulebook and a name mismatch both end with fix that file.
Warnings that keep the source active
Every rulebook is a live file, so a saved edit is enforced on the next tool call with nothing to publish. A broken edit is not silent either. A file that fails to parse or fails the schema is dropped with the invalid-rulebook error above, and a deleted file is dropped with the missing-file error. Both move the snapshot to
degraded.Salvaged policy.json or 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, invalid entries in both allow-path lists are dropped, and disabling overrides are dropped. Valid secret allow paths remain in the salvaged policy. The project file at
.cc-safety-net/policy.json is salvaged the same way, with one difference. audit is user scope only, so a project audit section is ignored with the diagnostic project policy audit settings are ignored; audit is user scope only. 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.
While the file has errors, the dashboard form shows the full defaults instead of the salvaged values. You cannot save until you repair the file. The repair action preserves each recognized valid setting. It replaces the full file with defaults only when it cannot parse the JSON.
Transparent-wrapper coverage gap
transparent_wrappers is declared in rule.json, not in a rulebook, and rule.json carries no digest. That has two consequences:
- A dropped rulebook keeps its scope’s wrappers, because
rule.jsonitself is still readable. - An unreadable
rule.jsonloses 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.
rule.json first when a scope is dropped for that reason.
Fail-closed cases
“Fail closed” describes runtime and analysis failures. It denies that one tool call. It does not describe 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.
Degraded-state reporting
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 auditconfigFallbackflag 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.
Visible and silent failures
- 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 statusa habit; rundoctorfor the full report. - An unmigrated legacy configuration is quieter. The runtime does not load the file and reports nothing about it, so the snapshot stays
readywhile those rules protect nothing.rule verifyflags it. - An invalid
policy.jsonmostly 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 invalidsafety.levelsilently falls back tostandard, so a typo inparanoidlowers your preset. Invalidsecret_protection.deny_pathsentries 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.
Recover configuration
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
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. This command is informational. Use it for routine checks, not as a gate.2
Get the full report
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
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
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. It ends with All configs valid. or Configs valid with warnings. when nothing is broken, and with Config validation failed. and exit 1 when something is. The command can make one change. 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 the source
A local rulebook needs no command. Edit the file the diagnostic names, and the guard reads it on the next tool call.For a remote source that is missing or stale, vendor it again:Re-resolves every configured remote source, writes each one to
rules/<name>/rulebook.json, then reloads that scope exactly as the guard loads it. Name one source to update only that one, and add --global for the user scope. If any diagnostic remains, it reports that diagnostic and exits non-zero instead of claiming success. Rule config updated. followed by the Active rulebooks (<n>): list means the scope has no remaining diagnostic.Each source updates independently. One that fails prints Failed to update <spec>: <message> and keeps the copy it already had, while the others still update. Verification covers only the scope being updated.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.status after each repair. The runtime reloads on the next tool call, so there is nothing to restart.
Migrate lock and cache leftovers
A scope configured by an earlier version can still carry arule.lock and a cache directory. Neither is read any more, so the snapshot stays ready and nothing is enforced from them. cc-safety-net doctor reports them as the info finding config.v2-leftovers, titled Rulebook lock and cache leftovers detected, with the detail Files an earlier version left behind are no longer read: <paths>. Its fix hint is Run `cc-safety-net rule sync` (add `--global` for user scope) to migrate them, then rerun doctor.
That migration is all rule sync does now. It runs offline and opens with its deprecation notice:
rule.json is missing or unreadable, because the lock is then the only record of the configured sources. See rule sync for every message it prints.
Migrate legacy inline rules
CC Safety Net no longer loads the legacy inline config files~/.cc-safety-net/config.json and .safety-net.json at runtime, and it emits no diagnostic about them. Their rules are not enforced at all while everything else keeps working, and the snapshot stays ready. This creates silent degradation after an upgrade. The session does not break, but those rules provide no protection and the runtime does not report the problem. Run cc-safety-net rule verify to find a legacy file that still needs migration.
Run the migration from the project whose legacy configuration you want to convert:
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.
Related pages
Policy
The complete
policy.json contract, defaults, and per-field salvage.Custom rules
Rulebook layout, sources, vendoring, 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.