fix(checkpoints): display failed deletes to users and stabilize result keys

Folds review findings: surface failed_deletes in CLI and gateway
/rollback output (new gateway.rollback.failed_deletes locale key, 17
locales), emit skipped_oversize on the nothing-to-restore early return
too, document all three report keys in the restore() docstring, and pin
the failed_deletes contract from both sides in tests.
This commit is contained in:
kshitijk4poor
2026-08-26 17:55:42 +05:30
committed by kshitij
parent 37200847d1
commit d0351e3230
21 changed files with 37 additions and 1 deletions
+8
View File
@@ -3487,6 +3487,14 @@ class GatewaySlashCommandsMixin:
"gateway.rollback.kept_oversize",
files=shown + more,
)
failed = result.get("failed_deletes") or []
if failed:
shown = ", ".join(failed[:5])
more = f" (+{len(failed) - 5})" if len(failed) > 5 else ""
msg += "\n" + t(
"gateway.rollback.failed_deletes",
files=shown + more,
)
return msg
return t("gateway.rollback.restore_failed", error=result["error"])
+5
View File
@@ -170,6 +170,11 @@ class CLICommandsMixin:
shown = ", ".join(oversize[:5])
more = f" (+{len(oversize) - 5} more)" if len(oversize) > 5 else ""
print(f" ↷ Kept (too large for checkpoints, no stored copy to revert to): {shown}{more}")
failed = result.get("failed_deletes") or []
if failed:
shown = ", ".join(failed[:5])
more = f" (+{len(failed) - 5} more)" if len(failed) > 5 else ""
print(f" ⚠️ Could not remove (left in place): {shown}{more}")
print(" A pre-rollback snapshot was saved automatically.")
# Also undo the last conversation turn so the agent's context
+1
View File
@@ -281,6 +281,7 @@ Future messages in this room will use that transcript until `/reset` or another
restored: "✅ Herstel na kontrolepunt {hash}: {reason}\n'n Voor-terugrol-momentopname is outomaties gestoor."
kept_user_edits: "↷ Jou handwysigings is behou: {files}\nGebruik /rollback <N> --all om dié ook te herstel."
kept_oversize: "↷ Behou (te groot vir kontrolepunte, geen gestoorde kopie om na terug te keer nie): {files}"
failed_deletes: "⚠️ Kon nie verwyder nie (in plek gelaat): {files}"
restore_failed: "❌ {error}"
diff:
+1
View File
@@ -301,6 +301,7 @@ gateway:
restored: "✅ استُعيد إلى نقطة التحقّق {hash}: {reason}\nحُفظت لقطة ما قبل التراجع تلقائيًا."
kept_user_edits: "↷ احتُفظ بتعديلاتك اليدوية: {files}\nاستخدم ‎/rollback <N> --all لاستعادتها أيضًا."
kept_oversize: "↷ احتُفظ به (أكبر من أن يُحفظ في نقاط التفتيش، لا توجد نسخة مخزنة للرجوع إليها): {files}"
failed_deletes: "⚠️ تعذّرت الإزالة (تُرك الملف في مكانه): {files}"
restore_failed: "❌ {error}"
diff:
+1
View File
@@ -281,6 +281,7 @@ Future messages in this room will use that transcript until `/reset` or another
restored: "✅ Auf Checkpoint {hash} wiederhergestellt: {reason}\nEin Pre-Rollback-Snapshot wurde automatisch gespeichert."
kept_user_edits: "↷ Deine manuellen Änderungen wurden behalten: {files}\nNutze /rollback <N> --all, um auch diese wiederherzustellen."
kept_oversize: "↷ Behalten (zu groß für Checkpoints, keine gespeicherte Kopie zum Zurücksetzen): {files}"
failed_deletes: "⚠️ Konnte nicht entfernt werden (an Ort und Stelle belassen): {files}"
restore_failed: "❌ {error}"
diff:
+1
View File
@@ -293,6 +293,7 @@ gateway:
restored: "✅ Restored to checkpoint {hash}: {reason}\nA pre-rollback snapshot was saved automatically."
kept_user_edits: "↷ Kept your hand-edits: {files}\nUse /rollback <N> --all to restore those too."
kept_oversize: "↷ Kept (too large for checkpoints, no stored copy to revert to): {files}"
failed_deletes: "⚠️ Could not remove (left in place): {files}"
restore_failed: "❌ {error}"
diff:
+1
View File
@@ -278,6 +278,7 @@ gateway:
restored: "✅ Restaurado al checkpoint {hash}: {reason}\nSe guardó automáticamente un snapshot previo al rollback."
kept_user_edits: "↷ Se conservaron tus ediciones manuales: {files}\nUsa /rollback <N> --all para restaurarlas también."
kept_oversize: "↷ Conservado (demasiado grande para los checkpoints, no hay copia guardada a la que revertir): {files}"
failed_deletes: "⚠️ No se pudo eliminar (se dejó en su lugar): {files}"
restore_failed: "❌ {error}"
diff:
+1
View File
@@ -281,6 +281,7 @@ Future messages in this room will use that transcript until `/reset` or another
restored: "✅ Restauré au point de contrôle {hash} : {reason}\nUn instantané pré-rollback a été enregistré automatiquement."
kept_user_edits: "↷ Vos modifications manuelles ont été conservées : {files}\nUtilisez /rollback <N> --all pour les restaurer aussi."
kept_oversize: "↷ Conservé (trop volumineux pour les checkpoints, aucune copie enregistrée vers laquelle revenir) : {files}"
failed_deletes: "⚠️ Suppression impossible (laissé en place) : {files}"
restore_failed: "❌ {error}"
diff:
+1
View File
@@ -285,6 +285,7 @@ Future messages in this room will use that transcript until `/reset` or another
restored: "✅ Aischurtha go seicphointe {hash}: {reason}\nSábháladh roghchóip réamh-rollback go huathoibríoch."
kept_user_edits: "↷ Coinníodh do chuid athruithe láimhe: {files}\nÚsáid /rollback <N> --all chun iad sin a aischur freisin."
kept_oversize: "↷ Coinnithe (ró-mhór do sheicphointí, níl aon chóip stóráilte le filleadh uirthi): {files}"
failed_deletes: "⚠️ Níorbh fhéidir a bhaint (fágtha ina áit): {files}"
restore_failed: "❌ {error}"
diff:
+1
View File
@@ -281,6 +281,7 @@ Future messages in this room will use that transcript until `/reset` or another
restored: "✅ Visszaállítva a(z) {hash} ellenőrzőpontra: {reason}\nA visszaállítás előtti pillanatkép automatikusan elmentve."
kept_user_edits: "↷ A kézi szerkesztéseid megmaradtak: {files}\nHasználd a /rollback <N> --all parancsot, hogy azokat is visszaállítsd."
kept_oversize: "↷ Megtartva (túl nagy az ellenőrzőpontokhoz, nincs tárolt másolat, amire vissza lehetne állni): {files}"
failed_deletes: "⚠️ Nem sikerült eltávolítani (a helyén maradt): {files}"
restore_failed: "❌ {error}"
diff:
+1
View File
@@ -281,6 +281,7 @@ Future messages in this room will use that transcript until `/reset` or another
restored: "✅ Ripristinato al checkpoint {hash}: {reason}\nUno snapshot pre-rollback è stato salvato automaticamente."
kept_user_edits: "↷ Le tue modifiche manuali sono state conservate: {files}\nUsa /rollback <N> --all per ripristinare anche quelle."
kept_oversize: "↷ Conservato (troppo grande per i checkpoint, nessuna copia salvata a cui tornare): {files}"
failed_deletes: "⚠️ Impossibile rimuovere (lasciato al suo posto): {files}"
restore_failed: "❌ {error}"
diff:
+1
View File
@@ -281,6 +281,7 @@ Future messages in this room will use that transcript until `/reset` or another
restored: "✅ チェックポイント {hash} に復元しました: {reason}\nロールバック前のスナップショットが自動的に保存されました。"
kept_user_edits: "↷ 手動編集は保持されました: {files}\nそれらも復元するには /rollback <N> --all を使用してください。"
kept_oversize: "↷ 保持しました(チェックポイントには大きすぎるため、戻せる保存コピーがありません): {files}"
failed_deletes: "⚠️ 削除できませんでした(そのまま残っています): {files}"
restore_failed: "❌ {error}"
diff:
+1
View File
@@ -281,6 +281,7 @@ Future messages in this room will use that transcript until `/reset` or another
restored: "✅ 체크포인트 {hash}(으)로 복원됨: {reason}\n롤백 전 스냅샷이 자동으로 저장되었습니다."
kept_user_edits: "↷ 직접 수정한 내용은 유지되었습니다: {files}\n해당 파일도 복원하려면 /rollback <N> --all 을 사용하세요."
kept_oversize: "↷ 유지됨 (체크포인트에 저장하기엔 너무 커서 되돌릴 저장본이 없습니다): {files}"
failed_deletes: "⚠️ 제거할 수 없었습니다 (그대로 남아 있음): {files}"
restore_failed: "❌ {error}"
diff:
+1
View File
@@ -281,6 +281,7 @@ Future messages in this room will use that transcript until `/reset` or another
restored: "✅ Restaurado para o checkpoint {hash}: {reason}\nFoi guardado automaticamente um snapshot anterior ao rollback."
kept_user_edits: "↷ As suas edições manuais foram mantidas: {files}\nUse /rollback <N> --all para restaurar essas também."
kept_oversize: "↷ Mantido (grande demais para os checkpoints, sem cópia guardada para reverter): {files}"
failed_deletes: "⚠️ Não foi possível remover (mantido no lugar): {files}"
restore_failed: "❌ {error}"
diff:
+1
View File
@@ -281,6 +281,7 @@ Future messages in this room will use that transcript until `/reset` or another
restored: "✅ Восстановлено до контрольной точки {hash}: {reason}\nСнимок перед откатом сохранён автоматически."
kept_user_edits: "↷ Ваши ручные правки сохранены: {files}\nИспользуйте /rollback <N> --all, чтобы восстановить и их."
kept_oversize: "↷ Сохранено (слишком большой для контрольных точек, нет сохранённой копии для отката): {files}"
failed_deletes: "⚠️ Не удалось удалить (файл оставлен на месте): {files}"
restore_failed: "❌ {error}"
diff:
+1
View File
@@ -281,6 +281,7 @@ Future messages in this room will use that transcript until `/reset` or another
restored: "✅ {hash} kontrol noktasına geri yüklendi: {reason}\nGeri alma öncesi anlık görüntü otomatik olarak kaydedildi."
kept_user_edits: "↷ Elle yaptığınız düzenlemeler korundu: {files}\nOnları da geri yüklemek için /rollback <N> --all kullanın."
kept_oversize: "↷ Korundu (denetim noktaları için çok büyük, geri dönülecek kayıtlı kopya yok): {files}"
failed_deletes: "⚠️ Kaldırılamadı (yerinde bırakıldı): {files}"
restore_failed: "❌ {error}"
diff:
+1
View File
@@ -281,6 +281,7 @@ Future messages in this room will use that transcript until `/reset` or another
restored: "✅ Відновлено до контрольної точки {hash}: {reason}\nЗнімок перед відкатом збережено автоматично."
kept_user_edits: "↷ Ваші ручні правки збережено: {files}\nВикористайте /rollback <N> --all, щоб відновити і їх."
kept_oversize: "↷ Збережено (завеликий для контрольних точок, немає збереженої копії для відкату): {files}"
failed_deletes: "⚠️ Не вдалося видалити (залишено на місці): {files}"
restore_failed: "❌ {error}"
diff:
+1
View File
@@ -281,6 +281,7 @@ Future messages in this room will use that transcript until `/reset` or another
restored: "✅ 已還原至檢查點 {hash}:{reason}\n已自動儲存回復前的快照。"
kept_user_edits: "↷ 已保留您的手動編輯:{files}\n若也要還原這些檔案,請使用 /rollback <N> --all。"
kept_oversize: "↷ 已保留(檔案過大無法納入檢查點,沒有可還原的儲存副本):{files}"
failed_deletes: "⚠️ 無法移除(保留原檔):{files}"
restore_failed: "❌ {error}"
diff:
+1
View File
@@ -281,6 +281,7 @@ Future messages in this room will use that transcript until `/reset` or another
restored: "✅ 已恢复到检查点 {hash}:{reason}\n已自动保存回滚前的快照。"
kept_user_edits: "↷ 已保留您的手动编辑:{files}\n如需一并恢复这些文件,请使用 /rollback <N> --all。"
kept_oversize: "↷ 已保留(文件过大无法纳入检查点,没有可恢复的存储副本):{files}"
failed_deletes: "⚠️ 无法移除(保留原文件):{files}"
restore_failed: "❌ {error}"
diff:
+2
View File
@@ -527,6 +527,7 @@ class TestSafeRestore:
assert not scratch.exists()
assert "scratch.txt" in result["restored_files"]
assert result["skipped_oversize"] == []
assert "failed_deletes" not in result
def test_safe_restore_does_not_report_a_failed_delete_as_restored(
self, mgr, work_dir, monkeypatch,
@@ -554,6 +555,7 @@ class TestSafeRestore:
assert result["success"] is True
assert stubborn.exists()
assert "stubborn.txt" not in result["restored_files"]
assert result["failed_deletes"] == ["stubborn.txt"]
def test_unsafe_restore_overwrites_everything(self, mgr, work_dir):
base = self._checkpoint(mgr, work_dir)
+5 -1
View File
@@ -1098,7 +1098,10 @@ class CheckpointManager:
With ``safe=True`` (full-directory restores only), files the user
hand-edited after Hermes' last write — per the agent-write ledger —
are left untouched, and only Hermes-authored changes are reverted.
The result gains ``skipped_user_edits`` listing the preserved paths.
The result gains ``skipped_user_edits`` listing the preserved paths,
``skipped_oversize`` listing paths kept because the size cap excluded
them from every checkpoint, and — only when a delete failed —
``failed_deletes`` listing paths that could not be removed.
"""
hash_err = _validate_commit_hash(commit_hash)
if hash_err:
@@ -1146,6 +1149,7 @@ class CheckpointManager:
"directory": abs_dir,
"restored_files": [],
"skipped_user_edits": skipped_user_edits,
"skipped_oversize": [],
}
# Take a pre-rollback snapshot so you can undo the undo.