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

  1. Pass mask in the range 0-7, or -1/omit it for automatic mask selection.
  2. Clamp or validate the value at the config/UI boundary before forwarding it.
  3. If the source is user input, parse to an integer and reject out-of-range values early with a friendly message.
  4. 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

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


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/97f6cad6f8790855. Report an issue: GitHub.