{"record":{"id":"f8ce4b05b8a09258","repo":"dagger/dagger","slug":"decode-length-truncated-varint","errorCode":null,"errorMessage":"decode length: truncated varint","messagePattern":"decode length: truncated varint","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"engine/clientdb/store_codec.go","lineNumber":96,"sourceCode":"\tif d.off == len(d.buf) {\n\t\treturn false, fmt.Errorf(\"decode bool: unexpected end of row\")\n\t}\n\tv := d.buf[d.off]\n\td.off++\n\tswitch v {\n\tcase 0:\n\t\treturn false, nil\n\tcase 1:\n\t\treturn true, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"decode bool: invalid value %d\", v)\n\t}\n}\n\nfunc (d *rowDecoder) length() (int, error) {\n\tv, n := binary.Uvarint(d.buf[d.off:])\n\tif n == 0 {\n\t\treturn 0, fmt.Errorf(\"decode length: truncated varint\")\n\t}\n\tif n < 0 {\n\t\treturn 0, fmt.Errorf(\"decode length: varint overflow\")\n\t}\n\td.off += n\n\tif v > uint64(maxInt) {\n\t\treturn 0, fmt.Errorf(\"decode length: %d overflows int\", v)\n\t}\n\treturn int(v), nil\n}\n\nfunc (d *rowDecoder) take(n int) ([]byte, error) {\n\tif n < 0 || n > len(d.buf)-d.off {\n\t\treturn nil, fmt.Errorf(\"decode field of length %d: only %d bytes remain\", n, len(d.buf)-d.off)\n\t}\n\tv := d.buf[d.off : d.off+n]\n\td.off += n\n\treturn v, nil","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/dagger/dagger/blob/82ba2681dbe30d3547a1dc50ea495900ab5b6047/engine/clientdb/store_codec.go#L78-L114","documentation":"rowDecoder.length decodes an unsigned varint used as a length prefix for string/bytes fields. binary.Uvarint returning n==0 means the buffer ran out before the length varint terminated. The string/bytes readers (which call length) cannot determine how many bytes to take, so the row is treated as corrupt.","triggerScenarios":"rowDecoder.string or rowDecoder.bytes called (from decodeSpan/decodeLog) on a row whose buffer ends inside the length prefix — truncated blob, short read from the store, or an offset pointing at the final bytes of a row.","commonSituations":"Partial row writes after a crash; reading a row that was clipped by a size limit; iterating rows with wrong boundaries so a decoder starts mid-row and runs out of bytes.","solutions":["Check row boundary computation in the caller: ensure each decodeSpan/decodeLog starts exactly at a row start, not mid-row.","Skip the corrupt/truncated row and re-ingest the span/log from the source.","Store row length explicitly and validate buf length before decoding.","Add encode/decode round-trip tests for string and bytes fields to catch writer truncation bugs."],"exampleFix":"// before\ns, err := dec.string() // panics-free but fails: truncated length varint\n// after\ns, err := dec.string()\nif err != nil {\n    return nil, fmt.Errorf(\"skipping corrupt row: %w\", err)\n}","handlingStrategy":"validation","validationCode":"if _, n := binary.Uvarint(buf[off:]); n == 0 {\n    return fmt.Errorf(\"incomplete length prefix at offset %d\", off)\n}","typeGuard":null,"tryCatchPattern":"s, err := dec.string()\nif err != nil {\n    return nil, fmt.Errorf(\"skip corrupt row: %w\", err)\n}","preventionTips":["Validate each row's full byte length before starting decode.","Iterate rows strictly by recorded boundaries, never by scanning bytes.","Fuzz encode/decode round trips to catch truncation bugs."],"tags":["go","codec","varint","corruption"],"backgroundTag":"truncated-varint","analyzedSha":"82ba2681dbe30d3547a1dc50ea495900ab5b6047","analyzedAt":"2026-09-05T07:21:37.930Z","contentChangedAt":"2026-09-05T07:21:37.930Z","schemaVersion":2},"datasetVersion":"2026-09-12T12:17:11.808Z"}