Hmbown/CodeWhale · error · Error

bad channel slug

Error message

bad channel slug

What it means

The sign subcommand derives the channel from --channel or the source document's channel field (default "stable") and validates it against CHANNEL_RE. A slug containing characters outside the allowed set (uppercase, slashes, spaces, etc.) throws "bad channel slug".

Solutions

  1. Use a slug accepted by CHANNEL_RE (check its definition near the top of the script; typically lowercase [a-z0-9-])
  2. Fix the channel field in the source JSON or pass --channel explicitly to override it
  3. Run `verify` on a known-good envelope to see valid channel examples

Example fix

// before
node facts-publish.mjs sign --channel "Beta/v2"
// after
node facts-publish.mjs sign --channel beta-v2
Defensive patterns

Strategy: validation

Validate before calling

const CHANNEL_RE = /^[a-z0-9-]+$/; // mirror the script's pattern
if (!CHANNEL_RE.test(channel)) throw new Error(`bad channel slug: ${channel}`);

Try / catch

try {
  await run(['sign', envelopePath, '--channel', channel]);
} catch (e) {
  if (e.message === 'bad channel slug') console.error('Channel must be a lowercase slug (letters, digits, hyphens)');
  throw e;
}

Prevention

When it happens

Trigger: Running `sign --channel <bad>` with a slug failing CHANNEL_RE, or signing a source JSON whose channel field is malformed/missing-safe default is overridden with an invalid value.

Common situations: Using a channel name like "Stable" or "beta/v2"; a docs/cloud-facts/stable.json edited with a typo'd channel; CI automation injecting a branch name as the channel.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/da97382889a1cf1a. Report an issue: GitHub.

Appendix: source

Thrown at web/scripts/facts-publish.mjs:536

    try { writeFileSync(fd, privateKey.export({ type: "pkcs8", format: "pem" })); }
    finally { closeSync(fd); }
    const raw = rawPublicKeyFromKeyObject(publicKey);
    console.log(JSON.stringify({
      key_id: keyId,
      algorithm: "ed25519",
      public_key_b64: raw.toString("base64"),
      public_key_bytes: [...raw],
      private_key_file: out,
      note: "Private key written with mode 0600. Move it into custody (password manager); never commit it.",
    }, null, 2));
    return 0;
  }
  if (cmd === "sign") {
    refuseUnderCi();
    const sourcePath = resolve(String(flags.source ?? resolve(REPO_ROOT, "docs/cloud-facts/stable.json")));
    const source = readJson(sourcePath);
    const channel = String(flags.channel ?? source.channel ?? "stable");
    if (!CHANNEL_RE.test(channel)) throw new Error("bad channel slug");
    const factsVersion = Number(flags["facts-version"] ?? source.facts_version);
    if (!Number.isSafeInteger(factsVersion) || factsVersion <= 0) throw new Error("--facts-version (or source.facts_version) must be a positive integer");
    const keyId = String(flags["key-id"] ?? "");
    const privateKey = loadPrivateKeyFromEnv();
    const publishedAt = String(flags["published-at"] ?? nowIso());
    const payload = buildPayload(source, { channel, factsVersion, publishedAt });
    const envelope = buildEnvelope({ privateKey, keyId, payload });
    const text = `${JSON.stringify(envelope, null, 2)}\n`;
    if (flags.out) {
      writeFileSync(resolve(String(flags.out)), text);
      console.error(`wrote ${flags.out} (channel=${channel} facts_version=${factsVersion} key_id=${keyId} sha256=${envelope.sha256})`);
    } else process.stdout.write(text);
    return 0;
  }
  if (cmd === "verify") {
    const envelope = readJson(resolve(String(positional[1] ?? "")));
    let pub = flags["public-key"];
    if (!pub) {

View on GitHub (pinned to 433685b202)