config-audit/commands/rollback.md
Kjell Tore Guttormsen 8f149891c9 fix(rollback): restore the backup path contract the engine and the commands disagreed on
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
2026-07-31 17:23:30 +02:00

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

  • $ARGUMENTS may 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 --raw is 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)

  1. Read the list of changes from ~/.claude/config-audit/backups/{backup-id}/manifest.yaml using the Read tool
  2. Show files that will be restored — ask for confirmation:
    AskUserQuestion:
      question: "Restore 3 files from backup 20260403_163045?"
      options:
        - "Yes, restore"
        - "Cancel"
    
  3. 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
  4. 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)
    
  5. 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 (or restoreBackup() returns a non-empty createdNotRemoved), 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}