NousResearch/hermes-agent · error · RangeError
loadout truncated
Error message
loadout truncated
What it means
A RangeError thrown by BitReader.bit() in apps/desktop/src/lib/loadout.ts:74 when a loadout decode reads past the end of the byte buffer — the bit position reached or exceeded buf.length*8. Loadout codes are bit-packed, DEFLATE-compressed payloads; the reader has no length-prefixed bound per field, so a structurally-short payload (or a read schema that disagrees with the write schema) runs off the end. The top-level decode() wraps schema-read failures as '<Noun> is malformed: ...', so end users usually see the wrapped form; 'loadout truncated' surfaces raw when BitReader is used directly.
Source
Thrown at apps/desktop/src/lib/loadout.ts:74
for (let i = 0; i < this.bits.length; i += 1) {
if (this.bits[i]) {
out[i >> 3]! |= 1 << (i & 7)
}
}
return out
}
}
export class BitReader {
private pos = 0
constructor(private readonly buf: Uint8Array) {}
bit(): number {
if (this.pos >= this.buf.length * 8) {
throw new RangeError('loadout truncated')
}
const i = this.pos++
return (this.buf[i >> 3]! >> (i & 7)) & 1
}
uint(width: number): number {
let v = 0
for (let i = 0; i < width; i += 1) {
v |= this.bit() << i
}
return v >>> 0
}
varint(): number {View on GitHub (pinned to c896c09c42)
Solutions
- Bump spec.version whenever the bit layout changes, so old codes fail the version check instead of reading off the end.
- Audit spec.read to consume exactly what spec.write emits (same fields, same widths, same order, varint/str pairs).
- If decoding user-pasted codes, rely on createLoadout's decode() which converts this into the friendly 'malformed' Err.
- When framing manually, slice with the correct header size before handing bytes to BitReader.
Example fix
// before — reader out of sync with writer
write: w => { w.uint(mode, 2) }
read: r => ({ mode: r.uint(4) }) // reads too many bits -> truncated on short payloads
// after — symmetric widths + version bump on any layout change
write: w => { w.uint(mode, 2) }
read: r => ({ mode: r.uint(2) }) Defensive patterns
Strategy: validation
Validate before calling
// round-trip property test: decode(encode(x)) deep-equals x const sample = makeRepresentativePayload() const code = encode(sample) const back = decode(code) assert.deepEqual(back, sample) // catches reader/writer asymmetry before users do
Try / catch
try {
return spec.read(new BitReader(inflateSync(payload)))
} catch (err) {
if (err instanceof RangeError && err.message === 'loadout truncated') throw new Err('Schema read ran past the payload — read/write asymmetry or truncated data')
throw err
} Prevention
- Bump spec.version with EVERY bit-layout change
- Keep spec.read the exact mirror of spec.write (order, widths, varint/str pairs)
- Always frame with the codec's encode()/decode(); never hand-slice buffers for BitReader
- Round-trip test every schema change with edge-case payloads
When it happens
Trigger: spec.read() calling uint/varint/str more times or with wider fields than spec.write() emitted; a payload sliced short (decode called on framed bytes shorter than the writer produced); version mismatch that changed field widths; manually constructing BitReader over a truncated subarray (e.g. wrong HEAD_BYTES split).
Common situations: Editing a loadout schema (adding a field) without bumping spec.version, so old codes decode with the new reader; a copy/paste of a share code that lost characters but still base64-decodes; hand-rolled framing changes; tests constructing readers from arbitrary buffers.
Related errors
- ${Noun} is malformed: ${err.message}
- That doesn't look like a ${noun}.
- ${Noun} is too short to be valid.
- ${Noun} is version ${version}; this build reads version ${sp
- ${Noun} looks corrupted (checksum mismatch).
AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14).
Data as JSON: /api/errors/f876fa1c26f54bab.
Report an issue: GitHub.