Hmbown/CodeWhale · error · Error

invalid draft id

Error message

invalid draft id

What it means

draftKey() builds KV keys of the form draft:<type>:<id> and refuses ids that fail /^[A-Za-z0-9._-]{1,128}$/ - empty, longer than 128 chars, or containing characters (colons, spaces, slashes, unicode, base64 '+'/'=') that would corrupt the key format or break parseDraftKey() round-tripping.

Source

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

export interface UsageLog {
  date: string;
  calls: number;
  inputTokens: number;
  outputTokens: number;
}

export interface DeepSeekEnv {
  baseUrl?: string;
  model?: string;
}

const AGENT_DRAFT_TYPE_SET = new Set<string>(AGENT_DRAFT_TYPES);
const DRAFT_ID_PATTERN = /^[A-Za-z0-9._-]{1,128}$/;

export function draftKey(type: AgentDraftType, id: string): string {
  if (!DRAFT_ID_PATTERN.test(id)) {
    throw new Error("invalid draft id");
  }
  return `draft:${type}:${id}`;
}

export function parseDraftKey(key: string): { type: AgentDraftType; id: string } | null {
  const match = /^draft:([^:]+):([^:]+)$/.exec(key);
  if (!match || !AGENT_DRAFT_TYPE_SET.has(match[1]) || !DRAFT_ID_PATTERN.test(match[2])) {
    return null;
  }
  return { type: match[1] as AgentDraftType, id: match[2] };
}

export function isAgentDraft(value: unknown): value is AgentDraft {
  if (!value || typeof value !== "object") return false;
  const draft = value as Record<string, unknown>;
  return (
    typeof draft.id === "string" &&
    DRAFT_ID_PATTERN.test(draft.id) &&

View on GitHub (pinned to 8880682c63)

Solutions

  1. Generate ids from a safe alphabet, e.g. crypto.randomUUID() (hyphens are allowed)
  2. Slugify free text before use: lowercase, replace [^a-z0-9._-]+ with '-', slice to 128
  3. Validate with the same regex in the UI and reject early with a friendly message

Example fix

// before
const key = draftKey('dispatch', rawTitle);

// after
const id = rawTitle.toLowerCase().replace(/[^a-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 128);
if (!id) throw new Error('draft id collapsed to empty after slugify');
const key = draftKey('dispatch', id);
Defensive patterns

Strategy: type-guard

Validate before calling

const DRAFT_ID = /^[A-Za-z0-9._-]{1,128}$/;
if (!DRAFT_ID.test(id)) {
  id = id.toLowerCase().replace(/[^a-z0-9._-]+/g, '-').slice(0, 128) || crypto.randomUUID();
}

Type guard

const DRAFT_ID_PATTERN = /^[A-Za-z0-9._-]{1,128}$/;
function isDraftId(id) {
  return typeof id === 'string' && DRAFT_ID_PATTERN.test(id);
}
// usage: if (!isDraftId(id)) return badRequest('invalid draft id');

Prevention

When it happens

Trigger: Passing user-supplied titles, URLs, or free text as the draft id; ids containing ':' which would shift key segments; an id that is empty after trimming.

Common situations: Using the chat subject or issue title directly as the id; base64-encoded ids; copy-paste ids with a trailing newline.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/06fe1d94a066e94c. Report an issue: GitHub.