thanos-io/thanos · error
parse block ID from parquet metadata file
Error message
parse block ID %q from parquet metadata file: %v
What it means
Wraps ulid.Parse on a string field (field 6, convertedFromBLIDs) inside the parquet conversion metadata protobuf: the recorded block ID is not a valid ULID, meaning the migration metadata itself is corrupt. The bad string and file path are included.
Solutions
- Re-generate the parquet metadata with a correct converter
- Verify ULID formatting in metadata
- Skip the offending metadata file
Defensive patterns
Strategy: type-guard
Validate before calling
if _, err := ulid.Parse(storedID); err != nil {
return fmt.Errorf("metadata %s holds invalid block id %q", path, storedID)
} Try / catch
id, err := ulid.Parse(u)
if err != nil {
log.Printf("skipping %s: bad ULID %q", path, u)
continue // or delete/regenerate the file
} Prevention
- Emit canonical 26-char uppercase ULIDs in the converter
- Round-trip test IDs on write
- Keep writer/reader versions aligned
- Log and quarantine files with unparseable IDs
When it happens
Trigger: ulid.Parse(u) fails inside the field-6 handler — the stored string is not a valid 26-character canonical ULID (empty, truncated, wrong encoding, or binary garbage).
Common situations: Corrupt or partially written parquet metadata; a writer that stored non-canonical ULIDs (lowercase or non-standard encoding) or a different ID format due to version skew.
Understand the failure class
Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.
Related errors
- id is not a valid block ULID, got
- ULID is not valid
- not a block dir
- failed to read parquet metadata from
- iterate bucket for parquet metadata
AI-assisted analysis of thanos-io/thanos@35b8b99117 (2026-09-07).
Data as JSON: /api/errors/7bebc814ea70711d.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/block/fetcher.go:1254
if err != nil {
return nil, errors.Wrapf(err, "read parquet metadata file: %v", path)
}
var fc easyproto.FieldContext
for len(content) > 0 {
content, err = fc.NextField(content)
if err != nil {
return nil, errors.Wrapf(err, "read next field from parquet metadata file: %v", path)
}
switch fc.FieldNum {
case 6:
u, ok := fc.String()
if !ok {
return nil, errors.Wrapf(err, "read convertedFromBLIDs field from parquet metadata file: %v", path)
}
id, err := ulid.Parse(u)
if err != nil {
return nil, errors.Wrapf(err, "parse block ID %q from parquet metadata file: %v", u, path)
}
migratedBlocks[id] = struct{}{}
}
}
return migratedBlocks, nil
}
// IgnoreDeletionMarkFilter is a filter that filters out the blocks that are marked for deletion after a given delay.
// The delay duration is to make sure that the replacement block can be fetched before we filter out the old block.
// Delay is not considered when computing DeletionMarkBlocks map.
// Not go-routine safe.
type IgnoreDeletionMarkFilter struct {
logger log.Logger
delay time.Duration
concurrency int
bkt objstore.InstrumentedBucketReader
View on GitHub (pinned to 35b8b99117)