Skip to content

fix(hack): seaweedfs naming audit reports a false clean when a kubectl LIST fails #3431

Closed

Description

@myasnikovdaniil

What happens

hack/seaweedfs-naming-audit.sh prints an empty table when a kubectl LIST fails, which is indistinguishable from the output for a genuinely clean fleet. Observed on a cluster where namespace-wide secret LISTs time out (>45s in tenant-root, cozy-system and default) while etcd and /readyz report healthy: the script exits 0 and reports nothing.

Why

system_releases() enumerates Helm releases with a namespace-wide secret LIST whose type filter is applied client-side, so the API returns every secret in the namespace — and stderr is discarded:

https://github.com/cozystack/cozystack/blob/main/hack/seaweedfs-naming-audit.sh#L95-L97

for rel in $(kubectl get secret -n "$1" \
               -o jsonpath='{range .items[?(@.type=="helm.sh/release.v1")]}{.metadata.labels.name}{"\n"}{end}' 2>/dev/null \
             | grep -E -- '-system$' | sort -u); do

A timeout produces empty stdout, the for body never runs, and the namespace is reported as having no SeaweedFS instances. The script has no error handling anywhere — there is no path that distinguishes "the query failed" from "nothing found".

The same fail-open shape applies to the other enumerations, so the blast radius is not limited to one call:

  • kubectl get ns -o name 2>/dev/null (whole-cluster mode) — a failure here audits zero namespaces and still prints an empty table
  • kubectl get pvc -n "$ns" -o name 2>/dev/null
  • kubectl get sts -n "$ns" -l app.kubernetes.io/name=seaweedfs -o name 2>/dev/null

Why it matters

This output is the documented gate in front of a destructive action. docs/operations/seaweedfs-431-rename-recovery.md makes it Step 1, and says of the script:

it is what the chart's refusal hands you to and acting on it deletes PVCs

It is also the verification step after the deletion, and the precondition for upgrading:

Re-run the audit. The tenant must now read L (or S, if you are on the Step 2 path). Only then upgrade.

Every tenant must read L or S in the audit before you start

So a silent false-clean is the exact output an operator is told to trust — both before deleting PVCs and when confirming the deletion worked. The same runbook notes that two earlier inline versions of this classification shipped wrong, which is why it became a script; this is a third failure in the same surface, one layer below the classification logic.

Test coverage

hack/seaweedfs-naming-audit.bats has 11 tests. All of them exercise classification — prefix reconstruction and generation direction. None injects a kubectl failure, so the runbook's "it is a tested script now" reassurance does not extend to the enumeration layer where this bug lives.

Suggested direction

Not a prescription, but the shape that seems right:

  1. Stop discarding stderr on the enumerations, or capture the exit status and abort loudly. "Could not determine" must be a distinct, non-zero outcome — never an empty row.
  2. Push the type filter server-side with --field-selector type=helm.sh/release.v1 instead of client-side jsonpath. That removes the full-namespace secret LIST that is timing out here, which addresses the trigger as well as the symptom. Note revisions() already uses a label selector (-l name=$2,owner=helm), so the script is inconsistent with itself on this point.
  3. Add a test that makes the fake kubectl fail and asserts the script exits non-zero rather than printing a clean table.

Activity

  1. added
    kind/bugCategorizes issue or PR as related to a bug
    area/storageIssues or PRs related to storage (linstor, seaweedfs, bucket, velero, harbor)
    on Jul 22, 2026
  2. added a commit that references this issue on Jul 28, 2026
    2c45377
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/storageIssues or PRs related to storage (linstor, seaweedfs, bucket, velero, harbor)kind/bugCategorizes issue or PR as related to a bug

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions