can1357/oh-my-pi · error
invalid mask ${mask}
Error message
invalid mask ${mask} What it means
encodeBytes accepts an explicit mask option (-1 means auto-select, 0-7 per the QR spec); any other value would produce an invalid QR code, so it validates eagerly and throws 'invalid mask <n>'.
Source
Thrown at packages/coding-agent/src/utils/qrcode.ts:212
if (usedBits <= capacityBits) break;
if (version >= maxVersion) {
throw new Error(`data too long for a QR code (${data.length} bytes, EC ${ecLevel})`);
}
}
const bits = new BitBuffer();
bits.append(BYTE_MODE_INDICATOR, 4);
bits.append(data.length, charCountBits(version));
for (const b of data) bits.append(b, 8);
const capacityBits = dataCodewords(version, ec.table) * 8;
bits.append(0, Math.min(4, capacityBits - bits.length)); // terminator
bits.append(0, (8 - (bits.length % 8)) % 8); // byte-align
for (let pad = 0; bits.length < capacityBits; pad ^= 1) bits.append(PAD_BYTES[pad]!, 8);
const codewords = QrCode.#interleave(bits.toBytes(), version, ec.table);
const mask = options?.mask ?? -1;
if (mask < -1 || mask > 7) throw new Error(`invalid mask ${mask}`);
return new QrCode(version, ecLevel, codewords, mask);
}
/** Split into blocks, append Reed-Solomon EC, and interleave per the spec. */
static #interleave(data: Uint8Array, version: number, ecTable: number): Uint8Array {
const numBlocks = NUM_EC_BLOCKS[ecTable]![version]!;
const eccLen = ECC_CODEWORDS_PER_BLOCK[ecTable]![version]!;
const rawCodewords = Math.floor(rawDataModules(version) / 8);
const numShort = numBlocks - (rawCodewords % numBlocks);
const shortLen = Math.floor(rawCodewords / numBlocks);
const divisor = rsDivisor(eccLen);
const blocks: Uint8Array[] = [];
const blockLen = shortLen + 1;
for (let i = 0, offset = 0; i < numBlocks; i++) {
const datLen = shortLen - eccLen + (i < numShort ? 0 : 1);
const dat = data.subarray(offset, offset + datLen);
offset += datLen;View on GitHub (pinned to 9690622007)
Solutions
- Pass mask in the range 0-7, or -1/omit it for automatic mask selection.
- Clamp or validate the value at the config/UI boundary before forwarding it.
- If the source is user input, parse to an integer and reject out-of-range values early with a friendly message.
- Default to omitting mask entirely unless deterministic mask choice is required (e.g. tests).
Example fix
// before: encodeBytes(data, { mask: 8 }); // after: validate mask is an integer in -1..7 (clamp or reject), then encodeBytes(data, { mask }); Defensive patterns
Strategy: validation
Validate before calling
function parseMask(v: unknown): number { const n = Number(v); if (!Number.isInteger(n) || n < -1 || n > 7) throw new Error(`mask must be -1..7, got ${String(v)}`); return n; } Type guard
function isValidMask(v: unknown): v is number { return typeof v === "number" && Number.isInteger(v) && v >= -1 && v <= 7; } Try / catch
try { const qr = encodeBytes(data, { mask: userMask }); } catch (err) { if (err instanceof Error && err.message.startsWith("invalid mask")) { return encodeBytes(data); /* fall back to auto mask */ } throw err; } Prevention
- Clamp/validate mask at the config or UI boundary before it reaches the encoder.
- Remember the valid range is -1 (auto) and 0-7 — off-by-one UIs exposing 1-8 are a common bug.
- Omit mask entirely unless deterministic output (tests, reproducibility) is required.
- Parse user input to an integer before forwarding option values.
When it happens
Trigger: Calling encodeBytes with options.mask set to a number outside -1..7 — e.g. 8, negative values other than -1, or NaN/garbage from unvalidated user input.
Common situations: Configuration plumbing passing an unparsed string or off-by-one index; UI exposing masks 1-8 instead of 0-7; user-supplied option not clamped before reaching the encoder.
Understand the failure class
Background: "must be positive", "Invalid value": how libraries reject invalid parameter values (ValueError, ArgumentError, INVALID_PARAMETER_VALUE) — this error's family across 28 libraries.
Related errors
- oldText must not be empty.
- At least one output ID is required
- Unrecognized source format. Did you mean './${source}' (loca
- app.surface must be a surface UUID (e.g. CMUX_SURFACE_ID), n
- launch program resolves to a directory: ${displayPath}. Pass
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/97f6cad6f8790855.
Report an issue: GitHub.