{"record":{"id":"f1be518acf0d63c4","repo":"Hmbown/CodeWhale","slug":"channel-version-and-reason-are-required","errorCode":null,"errorMessage":"--channel, --version and --reason are required","messagePattern":"--channel, --version and --reason are required","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/scripts/facts-publish.mjs","lineNumber":605,"sourceCode":"      published_by: String(flags[\"published-by\"] ?? \"\"),\n      notes: String(flags.notes ?? \"\"),\n    };\n    if (flags[\"dry-run\"]) {\n      console.log(JSON.stringify({ dry_run: true, channel: envelope.channel, facts_key: { key_id: envelope.key_id, public_key: pub }, facts_release: { ...row, payload_b64: `<${envelope.payload_b64.length} chars>` } }, null, 2));\n      return 0;\n    }\n    const channels = await postgrest(`facts_channel?scope=eq.global&slug=eq.${encodeURIComponent(envelope.channel)}&select=id`);\n    if (!channels?.length) throw new Error(`channel ${envelope.channel} does not exist`);\n    await postgrest(\"facts_key\", { method: \"POST\", body: { key_id: envelope.key_id, scope: \"global\", algorithm: \"ed25519\", public_key: pub, status: \"active\" }, prefer: \"resolution=ignore-duplicates,return=minimal\" });\n    const inserted = await postgrest(\"facts_release\", { method: \"POST\", body: { ...row, channel_id: channels[0].id }, prefer: \"return=representation\" });\n    console.log(JSON.stringify({ published: true, channel: envelope.channel, facts_version: envelope.facts_version, release_id: inserted?.[0]?.id ?? null, payload_sha256: inserted?.[0]?.payload_sha256 ?? null }, null, 2));\n    return 0;\n  }\n  if (cmd === \"revoke\") {\n    const channel = String(flags.channel ?? \"\");\n    const version = Number(flags.version);\n    const reason = String(flags.reason ?? \"\");\n    if (!CHANNEL_RE.test(channel) || !Number.isSafeInteger(version) || version <= 0 || !reason) throw new Error(\"--channel, --version and --reason are required\");\n    if (flags[\"dry-run\"]) {\n      console.log(JSON.stringify({ dry_run: true, channel, facts_version: version, status: \"revoked\", revoke_reason: reason }, null, 2));\n      return 0;\n    }\n    const channels = await postgrest(`facts_channel?scope=eq.global&slug=eq.${encodeURIComponent(channel)}&select=id`);\n    if (!channels?.length) throw new Error(`channel ${channel} does not exist`);\n    const updated = await postgrest(`facts_release?channel_id=eq.${channels[0].id}&facts_version=eq.${version}`, {\n      method: \"PATCH\",\n      body: { status: \"revoked\", revoked_at: nowIso(), revoke_reason: reason },\n      prefer: \"return=representation\",\n    });\n    console.log(JSON.stringify({ revoked: updated?.length ?? 0, channel, facts_version: version }, null, 2));\n    return 0;\n  }\n  throw new Error(`unknown command ${cmd}`);\n}\n\nconst invokedDirectly = process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url);","sourceCodeStart":587,"sourceCodeEnd":623,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/web/scripts/facts-publish.mjs#L587-L623","documentation":"The `revoke` subcommand of facts-publish validates its flags before touching the database. It throws this error when the --channel slug fails CHANNEL_RE, --version is not a positive safe integer, or --reason is empty/missing. It is a pre-flight CLI argument guard so no PostgREST call is made with garbage input.","triggerScenarios":"Running `facts-publish revoke` without --reason, with a channel slug containing characters outside CHANNEL_RE (spaces, uppercase, slashes), or with --version missing/non-numeric/zero/negative.","commonSituations":"Copy-pasting a channel display name instead of its slug, forgetting that version must be a positive integer (e.g. passing 'v12' instead of '12'), omitting --reason because it seems optional.","solutions":["Pass all three flags with valid values, e.g. `revoke --channel stable --version 42 --reason \"bad facts\"`","Check the channel slug matches the same format used when the channel was created (CHANNEL_RE)","Ensure --version is a plain positive integer","Run with --dry-run first to validate arguments without hitting the database"],"exampleFix":"// before\nnode web/scripts/facts-publish.mjs revoke --channel Stable --version v3\n// after\nnode web/scripts/facts-publish.mjs revoke --channel stable --version 3 --reason \"wrong facts published\"","handlingStrategy":"validation","validationCode":"const CHANNEL_RE = /^[a-z0-9-]+$/; // match the script's pattern\nif (!CHANNEL_RE.test(channel) || !Number.isSafeInteger(version) || version <= 0 || !reason) {\n  throw new Error(\"invalid revoke arguments\");\n}","typeGuard":null,"tryCatchPattern":"try {\n  await run([\"node\", \"web/scripts/facts-publish.mjs\", \"revoke\", \"--channel\", c, \"--version\", String(v), \"--reason\", r]);\n} catch (e) {\n  if (e.message.includes(\"--channel, --version and --reason\")) {\n    console.error(\"Fix flags: slug, positive integer version, non-empty reason\");\n  }\n}","preventionTips":["Always pass a non-empty --reason","Use the exact channel slug, not a display name","Pass --version as a bare integer, no 'v' prefix","Use --dry-run to validate arguments first"],"tags":["cli","validation","arguments"],"backgroundTag":"missing-required-flag","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-22T21:17:16.096Z"}