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
- Ensure the key was produced by draftKey() so it matches /^draft:<known-type>:<valid id>$/.
- On the API boundary, validate the key with parseDraftKey first and return 400 for invalid keys instead of throwing.
- If you only have type+id, rebuild the key via draftKey(type, id) before deleting.
- 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
- Always produce keys with draftKey(); never hand-assemble them.
- Validate keys at the HTTP boundary and return 400, keeping deleteDraft's throw as a backstop.
- Distinguish session keys from draft keys by prefix constant, never by convention.
- Write an integration test asserting deleteDraft refuses non-draft prefixes.
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
- invalid draft id
- Automation name is required
- Automation prompt is required
- coordinates must be finite numbers
- coordinates must be finite numbers
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)