Pipeline step 4 dogfood. `/config-audit rollback` could not see a single one of
the four real backups on this machine, and reported "Backup not found" for one
that was sitting right there.
Four defects, one root: nothing agreed on where a backup lives or what its
manifest looks like.
- M-BUG-22 `lib/backup.mjs` resolved `~/.config-audit/backups` (pre-v2.2.0)
while every command, agent and doc uses `~/.claude/config-audit/backups`.
The auto-backup hook and fix-cli wrote to the first, implement to the second,
rollback read only the first. Canonical root now, with the legacy root kept
readable so older backups stay listable and restorable (`legacy: true`).
- M-BUG-25 `parseManifest` understood only the engine's quoted `original_path:`
spelling, but implement hand-builds its manifest with `- backup:`/`original:`/
`sha256:`. Every implement-made backup parsed to zero files and restoreBackup
returned `{restored: [], failed: []}` — a success-shaped no-op. Both formats
parse now, and a manifest with unparseable entries throws instead of
pretending to succeed.
- M-BUG-23 both session hooks watched `~/.config-audit/sessions`, which does not
exist; sessions live under `~/.claude/`. "Check for active sessions" had never
fired once. It fires now.
- M-BUG-24 the suite called createBackup() against the developer's real home —
it had left nine stray backups there, and cleanupOldBackups() deletes past ten.
Root is overridable via CONFIG_AUDIT_BACKUP_ROOT; both test files use it.
Rollback still cannot delete files implement CREATED — no backup can hold a file
that never existed. It no longer does so silently: manifests carry a `created:`
list, restoreBackup returns `createdNotRemoved`, and rollback.md requires the
report. Automatic deletion is a destructive action and needs its own design.
Verified against backup 20260717_032636 on a throwaway copy: all three files
restore byte-exact (sha256 match), zero writes outside the copy, backup dir
unmodified. Suite 1382 -> 1398/0; frozen v5.0.0 snapshots untouched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SejM9RQAa1Hfuq7Ek2WfFr
4.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 - Show files that will be restored — ask for confirmation:
AskUserQuestion: question: "Restore 3 files from backup 20260403_163045?" options: - "Yes, restore" - "Cancel" - 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 - .claude/settings.json (checksum verified) - 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:
Never finish a restore without this section when the list is non-empty; a silently half-restored target reads as a clean rollback.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.
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}