restic/restic · warning
invalid start of string: %q
Error message
invalid start of string: %q
What it means
ID.UnmarshalJSON checked the total length (66 bytes) but the first byte is not a '"', so the value is not a JSON string. Per the json.Unmarshaler contract restic assumes it receives a well-formed JSON value, so this only happens when the bytes are malformed: leading whitespace plus a quote elsewhere, raw hex padded to 66 bytes, or binary garbage from a corrupted file. It signals input that never was valid JSON at that position.
Source
Thrown at internal/restic/id.go:87
func (id ID) MarshalJSON() ([]byte, error) {
buf := make([]byte, 2+hex.EncodedLen(len(id)))
buf[0] = '"'
hex.Encode(buf[1:], id[:])
buf[len(buf)-1] = '"'
return buf, nil
}
// UnmarshalJSON parses the JSON-encoded data and stores the result in id.
func (id *ID) UnmarshalJSON(b []byte) error {
// check string length
if len(b) != len(`""`)+hex.EncodedLen(idSize) {
return fmt.Errorf("invalid length for ID: %q", b)
}
if b[0] != '"' {
return fmt.Errorf("invalid start of string: %q", b[0])
}
// Strip JSON string delimiters. The json.Unmarshaler contract says we get
// a valid JSON value, so we don't need to check that b[len(b)-1] == '"'.
b = b[1 : len(b)-1]
_, err := hex.Decode(id[:], b)
if err != nil {
return fmt.Errorf("invalid ID: %s", err)
}
return nil
}
// IDFromHash returns the ID for the hash.
func IDFromHash(hash []byte) (id ID) {
if len(hash) != idSize {
panic("invalid hash type, not enough/too many bytes")View on GitHub (pinned to a80be1478a)
Solutions
- Use encoding/json APIs (json.Unmarshal, json.Decoder) to decode the enclosing structure instead of slicing raw bytes
- For repository files, run restic check and rebuild-index to regenerate damaged indexes
- Log the offending bytes to find what prefix is polluting the value
Defensive patterns
Strategy: validation
Validate before calling
if len(raw) == 66 && raw[0] != '"' {
return fmt.Errorf("ID field is not a JSON string (missing quote): %q", raw)
} Type guard
func isQuotedJSONString(raw []byte) bool { return len(raw) >= 2 && raw[0] == '"' } Prevention
- Use json.Decoder/json.Unmarshal on whole documents instead of slicing byte buffers by offset
- Validate JSON documents with a schema or shape check before feeding restic types
- Regenerate damaged repository JSON rather than patching bytes
When it happens
Trigger: Feeding a json.Decoder output offset wrong (decoding from the middle of a document); corrupted index/snapshot files where bytes shifted; manually constructed []byte that includes a prefix (e.g., 'id:' or a space) before the quoted ID.
Common situations: Custom parsers slicing JSON buffers by assumption instead of using json.Decoder token flow; files corrupted by truncated writes or bad sync jobs.
Related errors
- invalid length for ID: %q
- invalid ID: %s
- invalid overwrite behavior %q, must be one of (always|if-cha
- ParseSize: %q: %w
- invalid hash type, not enough/too many bytes
AI-assisted analysis of restic/restic@a80be1478a (2026-08-15).
Data as JSON: /api/errors/fc47ef73b2419ced.
Report an issue: GitHub.