Skip to content

fix(#554): auto-recover from a pending-* helm wedge in installer and auto-upgrade - #649

Merged
divyasinghds merged 7 commits into
developfrom
fix/554-wedge-recovery-clean
Aug 11, 2026
Merged

fix(#554): auto-recover from a pending-* helm wedge in installer and auto-upgrade#649
divyasinghds merged 7 commits into
developfrom
fix/554-wedge-recovery-clean

Conversation

@divyasinghds

@divyasinghdsdivyasinghds commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Fixes#554.

Problem

A helm process killed mid-operation (Ctrl-C, OOM, host reboot, laptop sleep) leaves the release in a pending-install/pending-upgrade state. The next helm upgrade --install then fails with "another operation is in progress"exit 1, not 124 — so the existing unwedge hint (guarded by -eq 124) never fires and nothing auto-recovers.

  • Site A — installer (scripts/lib/install-client-helm.sh): a prior killed run wedges every subsequent re-run with only a generic "installation failed".
  • Site B — auto-upgrade cronjob (client/templates/auto-upgrade-cronjob.yaml): a killed upgrade wedges every hourly tick forever, silently killing security-fix delivery until a human runs helm rollback.

Fix

Installer:

  • New _recover_pending_helm_release helper — reads helm status (jq-free), then helm rollback a pending-upgrade/pending-rollback, or helm uninstall a pending-install/uninstalling release, before the helm op.
  • Wired into both the normal install and adopt-reconcile paths; fails closed if the wedge can't be cleared rather than marching into it.
  • Surfaces the manual unwedge remedy on exit 1 too, not only the 124 timeout; adds --cleanup-on-fail to both upgrades.
  • The uninstall branch is data-safe: the chart renders helm.sh/resource-policy: keep on every PVC and helm reads it from the stored manifest, so PVCs survive (per docs/MIGRATIONS.md).

Cronjob:

  • Rolls back a pending-upgrade/pending-rollback wedge before retrying.
  • Switches --wait--atomic --cleanup-on-fail so a graceful failure self-reverts (the pre-flight rollback covers the abrupt-SIGKILL case --atomic cannot).

Chart bumped 1.9.28 → 1.9.29 (version + appVersion) so the fix actually publishes; manifest.sha256 regenerated.

Testing

  • bash -n + shellcheck clean; embedded cronjob script passes sh -n.
  • 113 installer bats tests pass (10 new: helper units, integration wedge-recovery, fail-closed, exit-1 hint).
  • 362 helm unittests pass (6 new cronjob-recovery assertions).
  • chart-version-guard.sh ✓; gen-manifest.sh --check ✓.

🤖 Generated with Claude Code


Note

Medium Risk
Changes install/upgrade/reconcile and security-fix auto-upgrade paths with destructive recovery (rollback/uninstall) and Helm flag changes; behavior is heavily tested but mistakes could affect live releases or credentials on adopt paths.

Overview
Fixes #554: a Helm process killed mid-operation leaves releases stuck in pending-* states, so later helm upgrade fails with another operation is in progress (exit 1, not timeout 124) and recovery never ran automatically.

Installer (install-client-helm.sh, assess.sh): Adds _recover_pending_helm_release to read helm status and rollbackpending-upgrade/pending-rollback or uninstallpending-install/uninstalling before install/reconcile; adopt/reconcile uses no-destroy so it won’t uninstall and drop stored credentials. detect_installed_client and reconcile now helm list with --deployed --failed --pending --uninstalling so wedged releases stay visible across Helm 3/4 defaults. The assess gate treats pending wedges as degraded (pending-wedge) instead of fast-path healthy. Upgrades gain --cleanup-on-fail; failure hints mention manual unwedge on exit 1 as well as timeout.

Auto-upgrade cronjob: Pre-upgrade rollback for pending-upgrade/pending-rollback; uninstalling skips the tick cleanly. Replaces --wait with --atomic --cleanup-on-fail.

Chart 1.9.28 → 1.9.29; manifest.sha256 updated; new bats/helm unittest coverage for recovery ordering and fail-closed behavior.

Reviewed by Cursor Bugbot for commit b771d56. Bugbot is set up for automated code reviews on this repo. Configure here.

…auto-upgrade
A helm process killed mid-operation (Ctrl-C, OOM, host reboot, laptop
sleep) leaves the release in a pending-install/pending-upgrade state.
The next `helm upgrade --install` then fails with "another operation is
in progress" — exit 1, not 124 — so the existing unwedge hint (guarded
by `-eq 124`) never fires and nothing auto-recovers. In the auto-upgrade
cronjob this silently kills security-fix delivery: every hourly tick
fails forever until a human runs `helm rollback`.
Installer (scripts/lib/install-client-helm.sh):
- add _recover_pending_helm_release: read `helm status` (jq-free) and
`helm rollback` a pending-upgrade/pending-rollback, or `helm uninstall`
a pending-install/uninstalling release, before the helm op runs.
- wire it into both the normal install and the adopt-reconcile paths;
fail closed if the wedge can't be cleared rather than marching into it.
- surface the manual unwedge remedy on exit 1 too, not only the 124
timeout; add --cleanup-on-fail to both upgrades.
- the uninstall branch is data-safe: the chart renders
helm.sh/resource-policy: keep on every PVC and helm reads it from the
stored manifest, so PVCs survive (docs/MIGRATIONS.md).
Auto-upgrade cronjob (client/templates/auto-upgrade-cronjob.yaml):
- roll back a pending-upgrade/pending-rollback wedge before retrying.
- switch --wait to --atomic --cleanup-on-fail so a graceful failure
self-reverts (the pre-flight rollback covers the abrupt-kill case
--atomic cannot).
Tests: 10 new installer bats cases (helper units, integration
wedge-recovery, fail-closed, exit-1 hint) and 6 new cronjob unittest
assertions. Chart bumped 1.9.28 -> 1.9.29 so the fix publishes;
manifest.sha256 regenerated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@divyasinghdsdivyasinghds self-assigned this Aug 10, 2026
Comment threadscripts/lib/install-client-helm.sh
Comment threadscripts/lib/install-client-helm.sh
Comment threadscripts/lib/install-client-helm.sh
…ad (Bugbot #649)
Bugbot review of the pending-* wedge recovery found three issues:
- Adopt recovery skipped pending releases (High): _reconcile_adopted_client
discovered the release with `helm list -A`, which on Helm 3 shows only
deployed releases unless states are named. A release wedged in pending-*
was invisible, so adopt fell through to a password prompt it can't satisfy.
- One-client guard blind to a wedge (High): detect_installed_client enumerated
with the same bare `helm list -A`, so a foreign client wedged in pending-*
was unseen — a re-run with a different clientId could overwrite it once
recovery cleared the wedge (the wedge itself used to block that overwrite).
- Recovery read unbounded (Medium): _recover_pending_helm_release ran an
unbounded `helm status`, so a wedged/unreachable API could hang a headless
run before the bounded upgrade.
Fixes:
- both enumeration sites now pass `--deployed --pending --failed`, so a wedged
or failed client stays visible to the adopt discovery and the one-client guard.
- bound the `helm status` READ with _bounded (the installer's timeout(1) probe
wrapper); it gates the rest, so a timeout yields empty status -> no-op -> the
bounded upgrade surfaces the API failure. The mutating rollback/uninstall are
deliberately NOT wrapped in a kill-based bound (SIGKILLing them midway would
recreate the wedge); uninstall stays bounded by helm's own --wait --timeout.
Tests: +1 guard regression (a DIFFERENT client wedged in pending-* is still
seen and blocked, asserting --pending is enumerated); the recovery unit +
integration tests override _bounded to reach the helm mock. manifest.sha256
regenerated. 114 installer bats pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment threadscripts/lib/install-client-helm.sh
…econcile path (Bugbot #649)
Bugbot: making pending-* releases visible to the adopt discovery (the previous
commit) newly exposed a credential-loss path. On a re-run after a killed FIRST
install, _reconcile_adopted_client now finds the pending-install release,
_recover_pending_helm_release uninstalls it (a never-deployed rev can't be
rolled back), then the reconcile runs `helm upgrade --reuse-values` (no
--install) -> release-not-found -> error. The adopted credential lived only in
that release (the account password is write-only on the backend), so it's gone.
Fix: add a no-destroy mode to _recover_pending_helm_release. The reconcile/adopt
path passes it: rollback (non-destructive, keeps --reuse-values valid) is still
performed, but the destructive uninstall branch is REFUSED and returns non-zero,
so the caller fails closed with a manual remedy instead of silently destroying
the sole copy of the credential. The normal install path keeps full mode — it
writes a fresh values file with the just-verified credential, so uninstalling a
pending-install there loses nothing.
Tests: +3 no-destroy unit cases (pending-install/uninstalling refused without an
uninstall; pending-upgrade still rolls back). manifest regenerated. 117 pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment threadscripts/lib/install-client-helm.sh Outdated
#649)
detect_installed_client now enumerates pending releases, so assess sees a
pending-upgrade release whose prior revision is still Ready and would classify
the machine healthy — handing off before install_client_helm runs recovery, so
the wedge stays dead. Add _assess_release_pending (bounded, jq-free) and degrade
to the normal flow on a wedge. +2 assess tests; existing classify tests mock the
new probe. manifest regenerated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment threadscripts/lib/install-client-helm.sh Outdated
… (Bugbot #649)
The installer pins Helm v4.2.3, whose `helm list` default lists ALL statuses;
Helm 3 defaults to deployed-only. Naming --deployed --failed --pending made the
listing version-independent but dropped `uninstalling` — a state
_recover_pending_helm_release already handles — so on Helm 4 a foreign client
stuck mid-uninstall became invisible to the one-client guard, re-opening the
overwrite fail-open. Add --uninstalling to both enumeration sites and to assess's
wedge probe, so the full deployed/failed/pending-*/uninstalling set is named
everywhere regardless of the helm default. manifest regenerated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment threadscripts/lib/assess.sh
Comment threadscripts/lib/install-client-helm.sh
…ard, real adopt remedy, cronjob uninstalling (Bugbot #649)
One pass instead of round-by-round. Fixes the two open Bugbot findings plus two
issues an adversarial self-audit surfaced first:
- assess `_assess_release_pending` failed OPEN: a helm error/timeout read as
"no wedge" and let the machine fast-path to healthy, skipping recovery. Now
captures the probe's exit code and degrades on any error — matching the
module's "never a false healthy" contract. (Bugbot)
- adopt no-destroy refusal printed only `helm rollback`, which cannot clear
pending-install/uninstalling and never said how to keep the write-only
credential. The helper now prints the credential-safe path (get values ->
uninstall -> re-run); the caller drops the misleading rollback hint. (Bugbot)
- `_status=$(_bounded helm status | awk)` lacked the house `|| _status=""`
errexit guard: safe today only because both callers use `if !` (which masks
set -e), a latent trap on any future bare call. Guarded. (audit)
- cronjob ignored `uninstalling`, so a killed `helm uninstall` failed every tick
with an opaque "another operation is in progress". Now skips cleanly with an
actionable log line. (audit)
Tests: +1 assess (probe fails closed on helm error), +1 installer (bare call
under set -e doesn't abort), no-destroy test asserts the credential-safe remedy,
+1 cronjob unittest (uninstalling handled). 38 assess + 118 installer bats +
cronjob unittest all pass. manifest regenerated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 7b0b8ca. Configure here.

Comment threadscripts/lib/install-client-helm.sh Outdated
…pe (Bugbot #649)
The prior `|| _status=""` guard fixed the set -e abort but RE-INTRODUCED the
SIGPIPE-under-pipefail wipe the codebase already learned about (_extract_yaml_value,
#525): on a WEDGED release awk's `exit` SIGPIPEs helm (141) as it writes the rest
of the status body, so under `set -o pipefail` the pipe is non-zero and the guard
wiped the correctly-parsed "pending-upgrade" — recovery then silently no-op'd and
re-runs still hit "another operation is in progress". Defeats the whole fix.
Capture the full `helm status` first (guarded), then parse it from a here-string
(no pipe, nothing can signal) — the same idiom _extract_yaml_value uses. Added a
regression test: a wedged status with a >64KB body under set -o pipefail must
still trigger rollback (fails on the old code, passes now). 119 installer + 38
assess bats pass; manifest regenerated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@divyasinghds
divyasinghds merged commit c60c4be into developAug 11, 2026
47 checks passed

@aptraceblocaptracebloc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@divyasinghdsApproved. Verified against a fresh clone of the head, not just the description.

  • R8 manifestshasum -a 256 -c scripts/manifest.sha256 → all 18 files OK (head b771d560); the client/ chart changes correctly sit outside the signed trust root.
  • bats:assess.bats 38/38, install-client-helm.bats 119/119 — matches the claimed counts, incl. the SIGPIPE-under-pipefail regression.
  • Logic:helm status parsed from a here-string (no early-exit awk pipe → no pipefail wipe), mutating rollback/uninstall deliberately unbounded, _assess_release_pending fails closed (any error → "wedged"), and no-destroy mode refuses the destructive uninstall to protect the stored credential. Sound and internally consistent.
  • All 9 Bugbot findings resolved (each with a fix commit + backing test); the last (pipefail status-parse) is on the current head and covered by installer-test 119, though Bugbot hasn't re-run on b771d560.

Note: this supersedes the now-closed #619 — smaller, more focused, adds the assess-gate degrade path + helm-unittest coverage #619 lacked. Code-owner @saadqbal already approved the current head, so it's mergeable.

🤖 Generated with Claude Code

@LukasWodka
LukasWodka deleted the fix/554-wedge-recovery-clean branch August 14, 2026 14:18
Sign up for freeto 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.

Interrupted helm op leaves permanent pending-* wedge — no auto-recovery in installer or auto-upgrade

4 participants

@divyasinghds@saadqbal@aptracebloc@LukasWodka