thanos-io/thanos · error · ApiError
ULID is not valid
Error message
ULID %q is not valid: %v
What it means
markBlock validates that the 'id' URL parameter of the block mark endpoint is a valid ULID (Thanos block IDs are ULIDs). If ulid.Parse fails, the request is rejected with ErrorBadData carrying the parse failure detail. It means the caller supplied a block ID that is not a well-formed ULID string.
Solutions
- Print the ID and validate it against a ULID regex (26 chars, Crockford base32) before sending
- List actual blocks via the blocks list API or bucket inspection to get a valid ID
- Check for shell quoting/interpolation issues that mangled the ID
- Ensure you are using the block ULID, not the block directory path including parent folders
Example fix
// before curl -X POST host/api/v1/blocks/bucket-01/no-compact // after curl -X POST host/api/v1/blocks/01ARZ3NDEKTSV4RRFFQ69G5FAV/no-compact
Defensive patterns
Strategy: validation
Validate before calling
const ulidRe = /^[0-9A-HJKMNP-TV-Z]{26}$/;
function isValidULID(id) { return ulidRe.test(id); }
if (!isValidULID(blockID)) throw new Error(`not a ULID: ${blockID}`); Type guard
function isULID(s) { return typeof s === 'string' && /^[0-9A-HJKMNP-TV-Z]{26}$/.test(s); } Try / catch
try {
const res = await fetch(`/api/v1/blocks/${id}/${action}`, {method:'POST'});
const body = await res.json();
if (body.errorType === 'bad_data') throw new Error(body.error);
} catch (e) {
if (/ULID .* is not valid/.test(e.message)) { /* re-prompt for valid block ID */ }
throw e;
} Prevention
- Validate IDs against the ULID regex before calling the mark endpoint
- Fetch block IDs from the blocks list API instead of typing them
- Beware shell interpolation/quoting when composing URLs
- Never pass bucket paths as block IDs
When it happens
Trigger: Calling POST/GET /api/v1/blocks/<id>/<action> (or markBlock via the blocks API) with an id param that is empty of ULID structure, e.g. a plain name like 'myblock', a truncated/typoed ULID, or a Prometheus TSDB directory path instead of a block ID.
Common situations: Scripting against the Thanos Query/Store blocks API with hand-written IDs; copying bucket paths instead of ULIDs; using old block IDs from a different TSDB layout; shell variable interpolation dropping characters from the ID.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- negative ' ' is not accepted. Try a positive integer
- unable to unmarshal config content
- endpoint
- unable to validate endpoints
- failed to parse the template
AI-assisted analysis of thanos-io/thanos@35b8b99117 (2026-09-07).
Data as JSON: /api/errors/a0cd3053bed02ff5.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/api/blocks/v1.go:114
func (bapi *BlocksAPI) markBlock(r *http.Request) (any, []error, *api.ApiError, func()) {
if bapi.disableAdminOperations {
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: errors.New("Admin operations are disabled")}, func() {}
}
idParam := r.FormValue("id")
actionParam := r.FormValue("action")
detailParam := r.FormValue("detail")
if idParam == "" {
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: errors.New("ID cannot be empty")}, func() {}
}
if actionParam == "" {
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: errors.New("Action cannot be empty")}, func() {}
}
id, err := ulid.Parse(idParam)
if err != nil {
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: errors.Errorf("ULID %q is not valid: %v", idParam, err)}, func() {}
}
actionType := parse(actionParam)
switch actionType {
case Deletion:
err := block.MarkForDeletion(r.Context(), bapi.logger, bapi.bkt, id, detailParam, promauto.With(nil).NewCounter(prometheus.CounterOpts{}))
if err != nil {
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}, func() {}
}
case NoCompaction:
err := block.MarkForNoCompact(r.Context(), bapi.logger, bapi.bkt, id, metadata.ManualNoCompactReason, detailParam, promauto.With(nil).NewCounter(prometheus.CounterOpts{}))
if err != nil {
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}, func() {}
}
default:
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: errors.Errorf("not supported marker %v", actionParam)}, func() {}
}
return nil, nil, nil, func() {}View on GitHub (pinned to 35b8b99117)