oven-sh/bun · error · Error
generate-string-map: ${name}: keys ${JSON.stringify(k)} coll
Error message
generate-string-map: ${name}: keys ${JSON.stringify(k)} collide under case folding What it means
With `emitCaseInsensitive: true`, the generator lowercases every key and keeps a `seenCi` set. Two distinct keys that fold to the same lowercase form ("Content-Type" and "content-type") would be indistinguishable to the generated `<name>_ignore_ascii_case` match, so the generator aborts instead of emitting ambiguous arms.
Source
Thrown at src/codegen/generate-string-map.ts:234
function buildOne<V>(spec: StringMapSpec<V>): string {
const { name, valueTy, entries, emitKeys, emitIndexOf, emitCaseInsensitive, doc } = spec;
const emitValue = spec.emitValue ?? ((v: V) => (typeof v === "string" ? JSON.stringify(v) : String(v)));
const seen = new Set<string>();
const seenCi = new Set<string>();
for (const [k] of entries) {
if (seen.has(k)) throw new Error(`generate-string-map: duplicate key ${JSON.stringify(k)} in ${name}`);
seen.add(k);
if (emitCaseInsensitive) {
// eslint-disable-next-line no-control-regex
if (!/^[\x00-\x7f]*$/.test(k)) {
throw new Error(
`generate-string-map: ${name}: emitCaseInsensitive requires ASCII keys; ${JSON.stringify(k)} is not`,
);
}
const lk = k.toLowerCase();
if (seenCi.has(lk)) {
throw new Error(`generate-string-map: ${name}: keys ${JSON.stringify(k)} collide under case folding`);
}
seenCi.add(lk);
}
}
const kvs: Array<readonly [Buffer, string]> = entries.map(([k, v]) => [Buffer.from(k, "utf8"), emitValue(v)]);
const out: string[] = [];
if (doc) for (const line of doc.split("\n")) out.push(`/// ${line}`.trimEnd());
emitLookup(out, name, valueTy, kvs, { ci: false });
if (emitCaseInsensitive) {
out.push(``);
emitLookup(out, `${name}_ignore_ascii_case`, valueTy, kvs, { ci: true });
}
if (emitIndexOf) {
// Index in *declaration order* — same as `<NAME>_KEYS` so
// `KEYS[index_of(k).unwrap()]` round-trips. u16 is plenty (asserted).View on GitHub (pinned to 8c5296ac45)
Solutions
- Delete one of the colliding entries (keep the canonical casing) — the case-insensitive lookup matches both anyway.
- Rename one key so the lowercase forms differ.
- Turn off `emitCaseInsensitive` if the two casings must map to different values.
Example fix
// before emitCaseInsensitive: true, entries: [ ["Content-Type", A], ["content-type", B], // collides under case folding ], // after emitCaseInsensitive: true, entries: [["Content-Type", A]],
Defensive patterns
Strategy: validation
Validate before calling
function assertNoCaseCollisions(name, entries, emitCaseInsensitive) {
if (!emitCaseInsensitive) return;
const seenCi = new Set();
for (const [k] of entries) {
const lk = k.toLowerCase();
if (seenCi.has(lk)) throw new Error(`${name}: ${k} collides under case folding`);
seenCi.add(lk);
}
} Prevention
- Pick one canonical casing convention (e.g. all-lowercase) for the whole table.
- When merging tables from multiple sources, normalize casing during the merge, not after.
- Remember the case-insensitive lookup makes casing variants redundant — never add both.
When it happens
Trigger: A case-insensitive spec containing two entries whose keys differ only in ASCII letter case.
Common situations: Adding an all-lowercase HTTP header name next to its canonical mixed-case form; merging tables from two sources that used different casing conventions.
Related errors
- generate-string-map: duplicate key ${JSON.stringify(k)} in $
- generate-string-map: ${name}: emitCaseInsensitive requires A
- ${name}: ${entries.length} entries exceed u16 indexOf range
- ${input}: missing default export
- file did not exist after write: ${outputPath}
AI-assisted analysis of oven-sh/bun@8c5296ac45 (2026-08-16).
Data as JSON: /api/errors/8b5fc2704fb0305b.
Report an issue: GitHub.