goharbor/harbor · error · Exception
purge upload age should set with with nh, n is the number of
Error message
purge upload age should set with with nh, n is the number of hour and n should not be less than 2
What it means
The SBOM report DAO's DeleteMany refuses to run when the query carries no keywords. With an empty query, orm.QuerySetter would match every row of the sbom report table, so this error is an explicit guard against an accidental full-table wipe. It is intentional data protection, not a bug.
Source
Thrown at make/photon/prepare/models.py:218
def __init__(self, config: dict):
if not config:
self.enabled = False
self.enabled = config.get('enabled')
self.age = config.get('age') or '168h'
self.interval = config.get('interval') or '24h'
self.dryrun = config.get('dryrun') or False
return
def validate(self):
if not self.enabled:
return
# age should end with h
if not isinstance(self.age, str) or not self.age.endswith('h'):
raise Exception('purge upload age should set with with nh, n is the number of hour')
# interval should larger than 2h
age = self.age[:-1]
if not age.isnumeric() or int(age) < 2:
raise Exception('purge upload age should set with with nh, n is the number of hour and n should not be less than 2')
# interval should end with h
if not isinstance(self.interval, str) or not self.interval.endswith('h'):
raise Exception('purge upload interval should set with with nh, n is the number of hour')
# interval should larger than 2h
interval = self.interval[:-1]
if not interval.isnumeric() or int(interval) < 2:
raise Exception('purge upload interval should set with with nh, n is the number of hour and n should not beless than 2')
return
class Cache:
def __init__(self, config: dict):
if not config:
self.enabled = False
self.enabled = config.get('enabled')
self.expire_hours = config.get('expire_hours')
View on GitHub (pinned to 7b2fd08cc5)
Solutions
- Always set at least one keyword (digest, registration_uuid, ...) before calling DeleteMany
- Assert the filter is non-empty in the layer above the DAO and fail with context
- Keep the guard as is — fix the empty input, never bypass the check
Example fix
// before
_, err := sbomDAO.DeleteMany(ctx, query) // query.Keywords may be empty
// after
if len(query.Keywords) == 0 {
return errors.New("refusing to delete sbom reports: no filter set")
}
_, err := sbomDAO.DeleteMany(ctx, query) Defensive patterns
Strategy: validation
Validate before calling
if len(query.Keywords) == 0 {
return fmt.Errorf("refusing bulk delete: no keywords in query")
}
return d.DeleteMany(ctx, query) Try / catch
if _, err := d.DeleteMany(ctx, query); err != nil {
if strings.Contains(err.Error(), "delete all sbom reports at once is not allowed") {
// guard fired: your filter construction is broken — never bypass it
return fmt.Errorf("cleanup filter empty; aborting delete")
}
return err
} Prevention
- Build delete filters from values you have already asserted non-empty
- Treat this error as a filter-construction bug, not as a DAO limitation
- Never attempt a full-table delete through this DAO
When it happens
Trigger: dao.DeleteMany(ctx, q.Query{}) or a q.Query whose Keywords map is nil/empty — e.g. a cleanup job built its digest filter conditionally and the condition never fired.
Common situations: GC or retention jobs constructing keyword filters from possibly-empty inputs; nil-vs-empty-map bugs after refactors; code ported from the vulnerability report DAO assuming different query semantics.
Related errors
- purge upload interval should set with with nh, n is the numb
- purge upload interval should set with with nh, n is the numb
- cache expire hours should be positive number
- invalid quota update provider: {}
- 127.0.0.1 can not be the hostname
AI-assisted analysis of goharbor/harbor@7b2fd08cc5 (2026-08-16).
Data as JSON: /api/errors/52610b904a564a74.
Report an issue: GitHub.