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

  1. Bump spec.version whenever the bit layout changes, so old codes fail the version check instead of reading off the end.
  2. Audit spec.read to consume exactly what spec.write emits (same fields, same widths, same order, varint/str pairs).
  3. If decoding user-pasted codes, rely on createLoadout's decode() which converts this into the friendly 'malformed' Err.
  4. 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

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


AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14). Data as JSON: /api/errors/f876fa1c26f54bab. Report an issue: GitHub.