Hmbown/CodeWhale · error · Error
invalid draft key
Error message
invalid draft key
What it means
deleteDraft() only deletes keys that round-trip through parseDraftKey(): exactly draft:<type>:<id> where type is in AGENT_DRAFT_TYPES and id matches the strict id pattern. Any other string - a bare id, a wrong prefix, an unknown type - is rejected before kv.delete() runs, protecting the namespace from arbitrary deletions.
Source
Thrown at web/lib/community-agent.ts:316
} 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 8880682c63)
Solutions
- Pass the exact key returned by listDrafts()/getDraft(), never a bare id
- Build keys only via draftKey(type, id)
- Guard with parseDraftKey() and skip/400 on non-draft keys
Example fix
// before await deleteDraft(kv, id); // id like 'abc123' -> throws // after const key = draftKey(type, id); // 'draft:dispatch:abc123' await deleteDraft(kv, key);
Defensive patterns
Strategy: type-guard
Validate before calling
import { parseDraftKey } from './community-agent';
if (parseDraftKey(key) === null) {
return new Response('invalid draft key', { status: 400 });
} Type guard
import { parseDraftKey } from './community-agent';
function isDraftKey(key) {
return typeof key === 'string' && parseDraftKey(key) !== null;
}
// usage: if (!isDraftKey(key)) continue; // skip foreign KV keys during sweeps Prevention
- Only delete keys produced by draftKey() or returned by listDrafts()
- When adding a draft type, add it to AGENT_DRAFT_TYPES in the same commit
- In admin routes, validate keys before any kv.delete() call
When it happens
Trigger: Passing the draft id instead of the full KV key; a key whose type segment is not one of the known draft types; sweeping admin routes that forward arbitrary keys.
Common situations: The front-end sends {id} and the handler forwards it as key; a new draft type was added to constants but not to AGENT_DRAFT_TYPES; maintenance scripts iterating the whole namespace.
Related errors
- invalid draft id
- ${name} is unavailable in Workflow scripts: runs must be det
- new Date()/Date() is unavailable in Workflow scripts: runs m
- task(): expected an options object
- ${envelope.error}
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/e7da7cd4c33c7f80.
Report an issue: GitHub.