Skip to content

fix(cli): a repair that repaired nothing says so - #762

Merged
blafourcade merged 1 commit into
nextfrom
fix/a-restore-that-repairs-nothing-says-so
Sep 4, 2026
Merged

blafourcade merged 1 commit into
nextfrom
fix/a-restore-that-repairs-nothing-says-so

Conversation

@blafourcade

Copy link
Copy Markdown
Contributor

What

aidd restore --force reported success while repairing nothing.

$ aidd restore --force
Checking claude for files to restore...
Warning: [config-restore] Use --force to overwrite modified files in non-interactive mode.
Nothing to restore — all files are unmodified.
exit=0
$ cat .claude/settings.json
{"MODIFIED":true}

Three false statements in one run: it asked for the flag it was given, it denied the drift it had just detected, and it reported success for a repair it never performed. Same project, same second, the sibling command repaired it: aidd ai restore --force → Restored 1 file.

Why

  • commands/restore.ts read --force into interactive and then dropped it; RestoreAllUseCase.execute had no force parameter at all.
  • restore-all-use-case.ts passed force: interactive, so with --force the use-case received force: false and ResolveRestoreDecisionUseCase threw on the first modified file.
  • The throw was caught into result.errors, printed as a warning, and the success line was emitted anyway.

aidd ai restore and aidd ide restore both plumb force: cmdOptions.force correctly. Only the top-level command lost it.

The smoke harness never caught it because its restore --force case asserts only exit 0, which the broken command satisfied.

After

$ aidd restore --force            → Restored 1 file(s), kept 0 file(s)   exit=0
$ aidd restore    (no TTY)        → Warning: … Use --force …             exit=1
$ aidd restore --force  (clean)   → Nothing to restore …                 exit=0

Guards

Guard Mutation that kills it
--force reaches the restore it delegates to force: interactive (the original)
the interactive checkbox is still the consent, nothing asked twice force: force
a run with errors exits non-zero and prints no success line drop the process.exit(1)

Each mutation killed its own guard and no other.

Gates: 3419 tests / 307 files, tsc, biome ci, knip, jscpd, bundle within budget, 368 repository script tests, 0 broken links, cli layering clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

`aidd restore --force` printed "Nothing to restore — all files are unmodified."
and exited 0 while the modified file it was asked to repair was still modified
on disk. The flag was read into `interactive` and then dropped, so the use-case
received `force: false`, refused to overwrite, and the resulting error was
swallowed into a warning under a success line.

`aidd ai restore` and `aidd ide restore` plumb the flag correctly; only the
top-level command lost it.

- `RestoreAllUseCase.execute` takes `force` and asks for an overwrite when
  either the interactive checkbox or `--force` supplied the consent.
- `restore` exits 1 when the run produced errors, instead of reporting the
  healthy state it did not reach.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
@blafourcade
blafourcade requested a review from a team as a code owner September 4, 2026 21:06
@blafourcade
blafourcade merged commit dccbef2 into next Sep 4, 2026
17 checks passed
blafourcade added a commit that referenced this pull request Sep 4, 2026
Four cases in `smoke-tools.sh` damaged a file and proved nothing about it.

They did not know which file they damaged. Four places picked one with
`find … | head -1`, and `find` answers in directory order — not sorted, not the
same on two filesystems — so each run corrupted whichever file came back first.

And the three restore cases never checked the repair: each appended drift, ran
`restore --force`, and asserted exit 0. A `restore --force` that exits 0 having
restored nothing is precisely the failure #762 fixed inside the command, so the
case meant to cover it would have passed throughout. The `ide` case was worse:
its drift was a bare newline, which `grep` cannot see, so no check on that file
was possible at all.

`first_file` (`LC_ALL=C sort | head -1`) replaces every unsorted pick. One named
marker, `SMOKE_DRIFT`, is what the three cases append, and `repaired` fails when
the mark is still there after the restore, or when nothing was drifted to repair.

The harness is not run by CI, so the three guards read its text, the way
`smoke-harness-isolation.unit.test.ts` already guards the home sandbox. Each was
written first and failed for the reason it names; `bash -n` and
`shellcheck -S error` are both clean.


Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

Co-authored-by: reference-week <[email protected]>
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
@blafourcade
blafourcade deleted the fix/a-restore-that-repairs-nothing-says-so branch September 6, 2026 06:22
@aidd-bot aidd-bot Bot mentioned this pull request Sep 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant