HeyPuter/puter · warning · HttpError

bad_request

bad_request

Error message

Missing `key`

What it means

Thrown by `#coerceKey` when the KV `key` argument is `null` or `undefined`. Every KV method runs its key through `#coerceKey`, so a missing key fails fast with HTTP 400 (legacyCode `bad_request`) before any storage/budget work. This mirrors the legacy controller error code for backward compatibility.

Source

Thrown at src/backend/drivers/kv/KVStoreDriver.ts:140

                    [DEFAULT_FREE_SUBSCRIPTION]: 3,
                    [DEFAULT_TEMP_SUBSCRIPTION]: 2,
                },
            },
        },
    };

    override getReportedCosts(): Record<string, unknown>[] {
        return Object.entries(KV_COSTS).map(([usageType, ucentsPerUnit]) => ({
            usageType,
            ucentsPerUnit,
            unit: 'capacity-unit',
            source: 'driver:kvStore',
        }));
    }

    #coerceKey(key: unknown): string {
        if (key === null || key === undefined) {
            throw new HttpError(400, 'Missing `key`', {
                legacyCode: 'bad_request',
            }); // legacyCode for backward compatibility with old error handling in controllers
        }
        const str = typeof key === 'string' ? key : String(key);
        if (str === '')
            throw new HttpError(400, 'Missing `key`', {
                legacyCode: 'bad_request',
            }); // legacyCode for backward compatibility with old error handling in controllers
        return str;
    }

    async #opts(method: string, args: KvCallArgs): Promise<KVOpts> {
        const actor = Context.get('actor') as Actor | undefined;

        // Every method resolves its options here first, so this is the one
        // place the budget gate has to go. `CREDIT_UNGATED_KV_METHODS` is what
        // stays reachable after it: an account that has run out still has to be
        // able to get its data out of the way of the next thing it stores.

View on GitHub (pinned to 908ec23eda)

Solutions

  1. Always pass a non-empty string key as the first argument.
  2. Default the key from a checked variable: `kv.set(key ?? '', value)` is wrong — ensure `key` is set upstream.
  3. Validate inputs at the call boundary before invoking KV.

Example fix

// before
kv.set(maybeUndefined, value);

// after
if (typeof key !== 'string' || !key) throw new Error('key required');
kv.set(key, value);
Defensive patterns

Strategy: validation

Validate before calling

function requireKey(key) {
  if (key === null || key === undefined) throw new Error('Missing key');
  return String(key);
}
kv.set(requireKey(key), value);

Type guard

/** @param {unknown} key @returns {boolean} */
function isPresentKey(key) { return key !== null && key !== undefined; }

Try / catch

try {
  await kv.set(key, value);
} catch (e) {
  if (e.code === 'bad_request' && e.message === 'Missing `key`') { /* fix the caller */ return; }
  throw e;
}

Prevention

When it happens

Trigger: Calling any KV method (`get`, `set`, `del`, `incr`, etc.) without a key or with `key: null`/`undefined`. E.g. `kv.set(undefined, value)` or `kv.get(null)`.

Common situations: A variable that was expected to hold a key but was never assigned; destructuring that produced undefined; a caller forgetting the key positional argument.

Related errors


AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12). Data as JSON: /api/errors/624762f4d34dc2ae. Report an issue: GitHub.