derailed/k9s · warning

chart is in an invalid state

Error message

chart is in an invalid state

What it means

Chart.diagnose (helm view) returns this when a release's status is anything other than 'deployed' — e.g. pending-install, pending-upgrade, failed, uninstalled, superseded. Caveat: if the passed object is not *ReleaseRes the code logs the type mismatch but keeps going and dereferences a nil Release, which panics — always pass *ReleaseRes.

Source

Thrown at internal/render/helm/chart.go:86

		render.ToAge(metav1.Time{Time: h.Release.Info.LastDeployed.Time}),
	}

	return nil
}

// Healthy checks component health.
func (c Chart) Healthy(_ context.Context, o any) error {
	h, ok := o.(*ReleaseRes)
	if !ok {
		slog.Error("Expected *ReleaseRes, but got", slogs.Type, fmt.Sprintf("%T", o))
	}

	return c.diagnose(h.Release.Info.Status.String())
}

func (Chart) diagnose(s string) error {
	if s != "deployed" {
		return fmt.Errorf("chart is in an invalid state")
	}

	return nil
}

// ----------------------------------------------------------------------------
// Helpers...

// ReleaseRes represents a helm chart resource.
type ReleaseRes struct {
	Release *release.Release
}

// GetObjectKind returns a schema object.
func (ReleaseRes) GetObjectKind() schema.ObjectKind {
	return nil
}

View on GitHub (pinned to 2d3ccc6ba2)

Solutions

  1. Inspect state: helm status <release> -n <ns> and helm history <release> -n <ns>
  2. For failed/pending-upgrade: helm rollback <release> <good-revision> or fix values and helm upgrade again
  3. If truly stuck: helm uninstall then reinstall (data loss for stateful workloads — drain first)
  4. Ensure callers pass *ReleaseRes so the nil-deref path cannot trigger

Example fix

// before: passes the wrong type -> nil deref after the !ok log
chart.Healthy(ctx, res)
// after
if rr, ok := res.(*ReleaseRes); ok {
    _ = chart.Healthy(ctx, rr)
}
Defensive patterns

Strategy: type-guard

Validate before calling

st := h.Release.Info.Status.String()
if st != "deployed" {
	// surface 'helm status <release>' instead of failing
}

Type guard

func isReleaseRes(o any) (*ReleaseRes, bool) {
	rr, ok := o.(*ReleaseRes)
	return rr, ok
}

Try / catch

Guard the type first (the library dereferences nil after a failed assertion); then map non-deployed statuses to remediation: rollback for failed, wait for pending.

Prevention

When it happens

Trigger: helm install/upgrade interrupted (pending-upgrade); a release whose hooks or pods failed (failed); listing releases right after upgrade before settle; --wait releases still converging.

Common situations: CI pipelines killing helm mid-upgrade leaving pending states; chart hooks failing; rolling back (superseded); timeouts on slow clusters.

Related errors


AI-assisted analysis of derailed/k9s@2d3ccc6ba2 (2026-08-15). Data as JSON: /api/errors/622d8321cb602da4. Report an issue: GitHub.