Hmbown/CodeWhale · error · Error

refusing to run with a secret under CI

Error message

refusing to run with a secret under CI (${marker} is set); publish from the founder's machine

What it means

refuseUnderCi scans CI_MARKERS environment variables and throws if any is set to a truthy value (0/false/no/off are allowed as opt-outs). The publish script must only run from the founder's machine where the signing secret lives, never inside CI where secrets and signing keys should not exist.

Solutions

  1. Run the publish script on the founder's machine with the CI markers unset (check `env | grep -i ci`)
  2. Explicitly unset the marker: `env -u CI -u GITHUB_ACTIONS node web/scripts/facts-publish.mjs ...`, or set it to an allowed falsy value like CI=0
  3. Do not bypass via CI=0 unless you truly are on the trusted local machine — the guard exists to keep the signing secret out of CI

Example fix

// before (CI)
node web/scripts/facts-publish.mjs
// after (local, markers cleared)
env -u CI -u GITHUB_ACTIONS node web/scripts/facts-publish.mjs
Defensive patterns

Strategy: try-catch

Validate before calling

const ciMarkers = ["CI", "GITHUB_ACTIONS", "TEAMCITY_VERSION", "BUILD_NUMBER"];
const active = ciMarkers.filter((m) => process.env[m] && !/^(0|false|no|off)$/i.test(process.env[m]));
if (active.length) throw new Error(`publishing blocked under CI markers: ${active.join(", ")} — run from the founder's machine`);

Try / catch

try {
  await publishFacts(envelope);
} catch (err) {
  if (err.message.includes("refusing to run with a secret under CI")) {
    console.error("Unset the CI marker named in the message and run the publish from the trusted local machine");
    process.exit(1);
  }
  throw err;
}

Prevention

When it happens

Trigger: Running facts-publish.mjs (or any code path that calls postgrest, which calls refuseUnderCi) while CI env vars like CI, GITHUB_ACTIONS, or TEAMCITY_VERSION are set to non-falsy values, e.g. in a GitHub Actions workflow, container with CI=true inherited, or a local shell that exported CI=1.

Common situations: A developer tried to automate publishing in CI; a local environment had CI=true exported globally from previous tooling; running inside a devcontainer or test runner that sets CI.

Understand the failure class

Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.

Related errors


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

Appendix: source

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

function loadTrustedKeysFromRepo() {
  const keys = parseTsKeys(readBoundedFile(resolve(WEB_ROOT, "lib/cloud-facts/keys.ts"), 64 * 1024).toString("utf8"));
  return new Map(keys.map((key) => [key.keyId, key]));
}

export function activePublishingKey(envelope, keys, now = Date.now()) {
  const key = validateTrustedKeys(keys).find((key) => key.keyId === envelope.key_id && key.status === "active");
  if (!key) throw new Error("primary signing key is not pinned and active; refusing publication");
  const check = verifyEnvelope(envelope, key.publicKey);
  if (!check.ok) throw new Error(`envelope does not verify: ${check.errors.join("; ")}`);
  if (!Number.isFinite(now) || utcTime(check.payload.published_at) > now + 300_000 ||
      (check.payload.not_after != null && utcTime(check.payload.not_after) <= now)) throw new Error("publication timestamp is future or expired");
  return { key, check };
}

function refuseUnderCi() {
  for (const marker of CI_MARKERS) {
    if (process.env[marker] && !/^(0|false|no|off)$/i.test(process.env[marker])) {
      throw new Error(`refusing to run with a secret under CI (${marker} is set); publish from the founder's machine`);
    }
  }
}

function sqlLiteral(value) {
  if (value === null || value === undefined) return "null";
  return `'${String(value).replace(/'/g, "''")}'`;
}

export function emitSql(envelope, { publishedBy = "", publicKeyB64, notes = "" }) {
  if (!publicKeyB64) throw new Error("public key required to emit the facts_key row");
  const check = verifyEnvelope(envelope, publicKeyB64);
  if (!check.ok) throw new Error(`envelope does not verify: ${check.errors.join("; ")}`);
  const payloadJson = Buffer.from(envelope.payload_b64, "base64").toString("utf8");
  return [
    "begin;",
    `insert into public.facts_key (key_id, scope, algorithm, public_key, status)`,
    `  values (${sqlLiteral(envelope.key_id)}, 'global', 'ed25519', ${sqlLiteral(publicKeyB64)}, 'active')`,

View on GitHub (pinned to 433685b202)