Hmbown/CodeWhale · error · Error
--channel, --version and --reason are required
Error message
--channel, --version and --reason are required
What it means
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.
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
Example fix
// before node web/scripts/facts-publish.mjs revoke --channel Stable --version v3 // after node web/scripts/facts-publish.mjs revoke --channel stable --version 3 --reason "wrong facts published"
Defensive patterns
Strategy: validation
Validate before calling
const CHANNEL_RE = /^[a-z0-9-]+$/; // match the script's pattern
if (!CHANNEL_RE.test(channel) || !Number.isSafeInteger(version) || version <= 0 || !reason) {
throw new Error("invalid revoke arguments");
} Try / catch
try {
await run(["node", "web/scripts/facts-publish.mjs", "revoke", "--channel", c, "--version", String(v), "--reason", r]);
} catch (e) {
if (e.message.includes("--channel, --version and --reason")) {
console.error("Fix flags: slug, positive integer version, non-empty reason");
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "--flag is required" and "must specify" CLI errors: how missing-required-flag validation works and how to fix it — this error's family across 20 libraries.
Related errors
- --attempts must be an integer from 1 to 20
- Choose one Runtime --thread ID.
- Command is required
- Invalid pet recording segment limit.
- Provide either --command or --url for `mcp add`.
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/f1be518acf0d63c4.
Report an issue: GitHub.
Appendix: source
Thrown at web/scripts/facts-publish.mjs:605
published_by: String(flags["published-by"] ?? ""),
notes: String(flags.notes ?? ""),
};
if (flags["dry-run"]) {
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));
return 0;
}
const channels = await postgrest(`facts_channel?scope=eq.global&slug=eq.${encodeURIComponent(envelope.channel)}&select=id`);
if (!channels?.length) throw new Error(`channel ${envelope.channel} does not exist`);
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" });
const inserted = await postgrest("facts_release", { method: "POST", body: { ...row, channel_id: channels[0].id }, prefer: "return=representation" });
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));
return 0;
}
if (cmd === "revoke") {
const channel = String(flags.channel ?? "");
const version = Number(flags.version);
const reason = String(flags.reason ?? "");
if (!CHANNEL_RE.test(channel) || !Number.isSafeInteger(version) || version <= 0 || !reason) throw new Error("--channel, --version and --reason are required");
if (flags["dry-run"]) {
console.log(JSON.stringify({ dry_run: true, channel, facts_version: version, status: "revoked", revoke_reason: reason }, null, 2));
return 0;
}
const channels = await postgrest(`facts_channel?scope=eq.global&slug=eq.${encodeURIComponent(channel)}&select=id`);
if (!channels?.length) throw new Error(`channel ${channel} does not exist`);
const updated = await postgrest(`facts_release?channel_id=eq.${channels[0].id}&facts_version=eq.${version}`, {
method: "PATCH",
body: { status: "revoked", revoked_at: nowIso(), revoke_reason: reason },
prefer: "return=representation",
});
console.log(JSON.stringify({ revoked: updated?.length ?? 0, channel, facts_version: version }, null, 2));
return 0;
}
throw new Error(`unknown command ${cmd}`);
}
const invokedDirectly = process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url);View on GitHub (pinned to 433685b202)