The chain observed configuration across repos but presented every write it then proposed as though it landed where the session stands. STATE named two arms; measuring found five, and two of them are worse than the two already known: - implement — the approval prompt named NO path at all, only a count, so a plan editing ~/.claude/CLAUDE.md and one editing ./CLAUDE.md produced byte-identical prompts. - rollback — the file list rendered `.claude/settings.json`, a repo-relative FORM, while the restore writes to the absolute original. The other arms were silent; this one pointed the wrong way. - fix — paths were visible but unclassified, and --global mixed machine-wide and project rows into one unmarked table. The gate's strength comes from the target's scope class, never from the command asking: five command-owned policies would drift apart the way five copies of the lever table did. SCOPE_CLASSES is one source for class, gate, wording and predicate; templates render `disclosures[]` from the CLI instead of restating what a class means. Two orderings in that table are load-bearing, and both were measured: - plugin-managed before user-scope. Both ~/.claude/config-audit/ and the legacy ~/.config-audit/ are live, and every command writes session state there. The other order fires the gate on every write ever made and gets it switched off, which is worse than no gate. - user-scope before cross-repo. ~/.claude/.git EXISTS, so a plain .git-upward walk answers "another repo" for ~/.claude/CLAUDE.md and silently downgrades the strongest gate on the subtraction axis's primary target to disclosure. disclose is not require-ok: campaign export is cross-repo by design, so the gate there says so rather than refusing. Distinct from require-target-dir.mjs, which asks whether a scan ROOT is readable (exit 3) — a different invariant, left unmerged along with its four inline copies. Also structural, both found while building this: the hand-maintained GUARDED list in the unknown-flag sweep now derives its completeness from the directory (measured complete at 14 of 14 first, so nothing was hiding — but the 15th CLI would have been swept by nothing); and prose shape-guards use whitespace- tolerant patterns, after one went red against a command file that did say the right thing, line-wrapped. Gated: implement, fix, rollback, plan, campaign export. Suite 1596 -> 1625/0, frozen v5.0.0 and default-output baselines 0 changed files. No new GAP dimension, no lever, no finding code — utilization denominators untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013941cEohSD5Aw56FVAtBgZ
5.1 KiB
| name | description | argument-hint | allowed-tools | model |
|---|---|---|---|---|
| config-audit:rollback | Restore configuration from backup — list available backups or rollback a specific one | [backup-id] | Read, Write, Glob, Grep, Bash, AskUserQuestion | sonnet |
Config-Audit: Rollback
Restore configuration files from a previous backup. Without arguments, lists available backups. With a backup ID, restores files from that backup.
Arguments
$ARGUMENTSmay contain a backup ID (format:YYYYMMDD_HHMMSS)--raw: pass-through flag accepted for CLI surface consistency. Rollback is file restoration only (no scanner output, no findings prose), so--rawis a no-op here, but the flag is still parsed so users get uniform behaviour across the toolchain.
Behavior
List mode (no argument)
Parse flags and list available backups from ~/.claude/config-audit/backups/:
RAW_FLAG=""
if echo "$ARGUMENTS" | grep -q -- "--raw"; then RAW_FLAG="--raw"; fi
ls -1 ~/.claude/config-audit/backups/
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Available Backups
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. 20260403_163045 — 3 files (settings.json, hooks.json, typescript.md)
2. 20260403_141230 — 1 file (CLAUDE.md)
3. 20260402_092015 — 5 files (full audit)
Usage: /config-audit rollback 20260403_163045
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Use the Read tool on each backup's manifest.yaml (the list of changes captured at backup time) to extract the file list and timestamps.
Restore mode (with backup ID)
-
Read the list of changes from
~/.claude/config-audit/backups/{backup-id}/manifest.yamlusing the Read tool -
Classify the
original:paths before showing them. A restore writes to the absolute path recorded at backup time, which may be machine-wide even when the backup was taken from a project — so the file list must be rendered as the absolute originals, never shortened to a repo-relative-looking form that implies the write stays local:node ${CLAUDE_PLUGIN_ROOT}/scanners/write-scope-cli.mjs --target "<original-1>" --target "<original-2>" --repo "$PWD" --output-file /tmp/config-audit-rollback-scope.json 2>/dev/null; echo $?Exit 0 = classified; 3 = argument error (show the stderr message). Read
/tmp/config-audit-rollback-scope.json, render each distinct string indisclosures[]verbatim, then ask for confirmation.When
requiresApprovalis false:AskUserQuestion: question: "Restore 3 files from backup 20260403_163045?" options: - "Yes, restore" - "Cancel"When
requiresApprovalis true, name the scope and put the safe option first:AskUserQuestion: question: "This restores {K} of 3 files to locations outside this project. Restore all 3?" options: - "Cancel" - "Yes — restore, including outside this project" -
For each file in the list of changes: a. Read the backup file from
~/.claude/config-audit/backups/{backup-id}/files/{safeName}b. Write to the original path c. Verify the checksum matches the recorded value in the list of changes -
Show result:
Restored 3 files from backup 20260403_163045 - /abs/path/.claude/settings.json (checksum verified) - /abs/path/hooks/hooks.json (checksum verified) - .claude/rules/typescript.md (checksum verified) -
Report what rollback cannot undo. A backup only holds files that already existed, so files the implement step CREATED survive the restore. If the manifest has a
created:section (orrestoreBackup()returns a non-emptycreatedNotRemoved), list those paths and say plainly that they remain:Left in place — created by implement, no backup exists: - .claude/rules/post-quality.md - guidelines/posting-rhythm.md Remove them manually if you want the pre-implement state exactly.Never finish a restore without this section when the list is non-empty; a silently half-restored target reads as a clean rollback.
Delete mode
If user says "delete" after listing, confirm and remove the backup directory.
Implementation
Use the backup and rollback libraries directly:
import { listBackups, restoreBackup, deleteBackup } from '../scanners/rollback-engine.mjs';
import { parseManifest, getBackupDir } from '../scanners/lib/backup.mjs';
Both read ~/.claude/config-audit/backups and fall back to the pre-v2.2.0
~/.config-audit/backups, so a backup made before the move still resolves;
listBackups() flags those with legacy: true. Prefer this API over ad-hoc
cp — it verifies the checksum before and after each write.
Or via Bash:
# List backups
ls -1 ~/.claude/config-audit/backups/
# Read manifest
cat ~/.claude/config-audit/backups/{id}/manifest.yaml
# Restore (copy back)
cp ~/.claude/config-audit/backups/{id}/files/{safeName} {originalPath}