Hmbown/CodeWhale · error

invalid draft key

Error message

invalid draft key

What it means

deleteDraft validates the key with parseDraftKey before deleting from KV; if the key does not match `draft:<type>:<id>` (or its type is not a known AgentDraftType), it throws "invalid draft key". This stops arbitrary strings — including session or other namespace keys — from being deleted through the draft API.

Solutions

  1. Ensure the key was produced by draftKey() so it matches /^draft:<known-type>:<valid id>$/.
  2. On the API boundary, validate the key with parseDraftKey first and return 400 for invalid keys instead of throwing.
  3. If you only have type+id, rebuild the key via draftKey(type, id) before deleting.
  4. Migrate any legacy-format keys stored in KV to the current draft: prefix scheme.

Example fix

// before
await deleteDraft(kv, body.key); // may throw

// after
const parsed = parseDraftKey(body.key);
if (!parsed) return json({ error: 'invalid draft key' }, 400);
await deleteDraft(kv, body.key);
Defensive patterns

Strategy: validation

Validate before calling

const parsed = parseDraftKey(key);
if (!parsed) return json({ error: 'invalid draft key' }, 400);
await deleteDraft(kv, key);

Type guard

const isDraftKey = (key) => typeof key === 'string' && /^draft:(plan|<other-types>):[A-Za-z0-9._-]{1,128}$/.test(key);

Prevention

When it happens

Trigger: Calling deleteDraft(kv, key) with a key like 'session:admin:x', a bare id without the 'draft:type:' prefix, an unknown draft type segment, or an id containing an extra colon.

Common situations: Frontend sending the whole stored key including stale format; mixing up the session prefix with draft keys; an older client writing keys in a since-changed format; passing user input straight through as a key.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/e7da7cd4c33c7f80. Report an issue: GitHub.

Appendix: source

Thrown at web/lib/community-agent.ts:317

  } catch {
    return null;
  }
}

export async function listDrafts(kv: KVNamespace | undefined, prefix = "draft:"): Promise<AgentDraft[]> {
  if (!kv) return [];
  const listed = await kv.list({ prefix, limit: 100 });
  const drafts: AgentDraft[] = [];
  for (const k of listed.keys) {
    const draft = await getDraft(kv, k.name);
    if (draft) drafts.push(draft);
  }
  return drafts;
}

export async function deleteDraft(kv: KVNamespace | undefined, key: string): Promise<void> {
  if (!kv) return;
  if (!parseDraftKey(key)) throw new Error("invalid draft key");
  await kv.delete(key);
}

// --- Admin session helpers ---

const SESSION_PREFIX = "session:admin:";
const SESSION_TTL_SEC = 60 * 60 * 24; // 24h

function toBase64Url(bytes: Uint8Array): string {
  let s = "";
  for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]);
  return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}

export async function safeEqual(a: string, b: string): Promise<boolean> {
  const enc = new TextEncoder();
  const ha = new Uint8Array(await crypto.subtle.digest("SHA-256", enc.encode(a)));
  const hb = new Uint8Array(await crypto.subtle.digest("SHA-256", enc.encode(b)));

View on GitHub (pinned to 433685b202)