Skip to content

fix(backup): bound the on-delete finalizer so a failing snapshot can't make an instance undeletable - #94

Merged
stubbi merged 1 commit into
mainfrom
fix/backup-finalizer-bounded
Jun 19, 2026
Merged

fix(backup): bound the on-delete finalizer so a failing snapshot can't make an instance undeletable#94
stubbi merged 1 commit into
mainfrom
fix/backup-finalizer-bounded

Conversation

@stubbi

@stubbi stubbi commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

Fixes #93.

Problem

BackupReconciler.HandleDeletion requeued forever when the final backup Job failed (or hung): it recorded the failure and requeued every 30s but never released the hermes.agent/backup-on-delete finalizer. So a HermesInstance with spec.backup.onDelete and bad/unreachable S3 creds became permanently undeletable (stuck Terminating, which also wedges its namespace). This is what hung the conformance backup-enabled cleanup.

Fix

Bound the deletion block with a grace window (finalBackupDeadline, 30 min from deletionTimestamp). Once it elapses, the operator gives up:

  • records status.backup.lastFailureReason = FinalBackupDeadlineExceeded,
  • emits a Warning FinalBackupAbandoned event (surfacing the data-loss risk),
  • releases the finalizer so deletion proceeds.

The hermes.agent/skip-final-backup=true annotation remains the explicit, immediate escape hatch. Within the window the behavior is unchanged (attempt the snapshot, hold the finalizer).

Tests

Fake-client unit tests: deadline exceeded → finalizer released (instance GC'd); within the window → finalizer held + final backup Job created. Full controller envtest suite + golangci-lint green. Docs updated.

🤖 Generated with Claude Code

…t block delete

Fixes #93. HandleDeletion requeued forever when the final backup Job failed (or
hung), so a HermesInstance with spec.backup.onDelete and bad/unreachable S3 creds
became permanently undeletable (stuck Terminating, which also wedges namespace
deletion).

Add a bounded grace window (finalBackupDeadline, 30m from deletionTimestamp): once
it elapses, the operator gives up — records status.backup.lastFailureReason=
FinalBackupDeadlineExceeded, emits a Warning FinalBackupAbandoned event, and
releases the finalizer so deletion proceeds (the snapshot is not taken; the event
surfaces the data-loss risk). The skip-final-backup annotation remains the
explicit immediate escape hatch. finalBackupDeadline is a var so tests can shrink
it.

Tests (fake client): deadline exceeded -> finalizer released; within the window ->
finalizer held + final backup Job created. Docs updated (bounded grace window +
troubleshooting).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@stubbi
stubbi enabled auto-merge (squash) June 19, 2026 09:55
@stubbi
stubbi merged commit 40d6099 into main Jun 19, 2026
13 checks passed
@stubbi
stubbi deleted the fix/backup-finalizer-bounded branch June 19, 2026 09:58
stubbi pushed a commit that referenced this pull request Jun 19, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.1.18](v0.1.17...v0.1.18)
(2026-06-19)


### Features

* **runtime:** run agents on the upstream s6 image — instance reaches
Ready (gateway API + /health)
([#90](#90))
([12dccb4](12dccb4))


### Bug Fixes

* **agent-image:** ship pyproject.toml + uv.lock at /opt/venv-template/
([#68](#68))
([#85](#85))
([39ef52c](39ef52c))
* **agent-image:** ship the uv binary in the runtime stage
([#68](#68))
([#88](#88))
([13fb1e5](13fb1e5))
* **backup:** bound the on-delete finalizer so a failing snapshot can't
make an instance undeletable
([#94](#94))
([40d6099](40d6099))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: paperclip-release-bot[bot] <288053502+paperclip-release-bot[bot]@users.noreply.github.com>
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.

backup.onDelete finalizer can make a HermesInstance undeletable when the snapshot fails

1 participant