paperclipai/paperclip · error · Error

Usage: publish-announcements.ts [directory] [--staging…

Error message

Usage: publish-announcements.ts [directory] [--staging name] [--dry-run | --publish]

What it means

parseAnnouncementPublishArgs throws its usage string when the --publish or --dry-run flag appears more than once, or in a conflicting combination. The mode variable may hold only one value; a duplicate or second mode flag is a usage error.

Solutions

  1. Pass exactly one mode flag: either --publish or --dry-run, not both.
  2. Audit shell scripts/aliases for duplicate mode flags being appended.
  3. Default behavior with no flag is a dry-run unless --publish is given; drop the redundant flag.
  4. Wrap the call in argument validation that dedupes flags before parsing.

Example fix

// before
publish-announcements.ts announcements --dry-run --publish
// after
publish-announcements.ts announcements --publish
Defensive patterns

Strategy: validation

Validate before calling

const modeFlags = args.filter(a => a === '--publish' || a === '--dry-run');
if (modeFlags.length > 1) throw new Error('Pass at most one of --publish / --dry-run');

Type guard

null

Try / catch

try {
  const parsed = parseAnnouncementPublishArgs(args);
} catch (e) {
  if (String(e.message).startsWith('Usage:')) {
    console.error(e.message + ' (exactly one of --publish or --dry-run)');
    process.exit(1);
  }
  throw e;
}

Prevention

When it happens

Trigger: Running the CLI with both `--publish` and `--dry-run`, or repeating the same flag (e.g. `--dry-run --dry-run`), so the second occurrence finds mode already set.

Common situations: Script templates concatenating flag arrays so the mode flag appears twice; users passing both flags thinking one overrides the other; shell aliases appending --dry-run while the user also passes --publish.

Understand the failure class

Background: "mutually exclusive" flag errors: what "can't supply both nx and xx", "--raw is not compatible with -i" and "cannot be used with" mean, and how to fix them — this error's family across 29 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/453ee83c9d4366bf. Report an issue: GitHub.

Appendix: source

Thrown at scripts/publish-announcements.ts:27

import { validateAnnouncementAnimation } from "../server/src/services/announcement-animation.js";

export function announcementPublishPrefix(staging?: string, hostPrefix?: string) {
  if (hostPrefix !== undefined && !/^[a-z0-9-]+(?:\/[a-z0-9-]+)*$/.test(hostPrefix)) {
    throw new Error("Invalid PAPERCLIP_PAGE_DEFAULT_PREFIX: use lowercase path segments without leading or trailing slashes");
  }
  const prefix = staging === undefined ? "announcements/v1" : `announcements/staging/${announcementIdSchema.parse(staging)}/v1`;
  return hostPrefix ? `${hostPrefix}/${prefix}` : prefix;
}

export function parseAnnouncementPublishArgs(args: string[]) {
  let sourceDirectory: string | undefined;
  let staging: string | undefined;
  let mode: "publish" | "dry-run" | undefined;
  const usage = "Usage: publish-announcements.ts [directory] [--staging name] [--dry-run | --publish]";
  for (let index = 0; index < args.length; index++) {
    const arg = args[index];
    if (arg === "--publish" || arg === "--dry-run") {
      if (mode) throw new Error(usage);
      mode = arg === "--publish" ? "publish" : "dry-run";
    } else if (arg === "--staging") {
      if (staging !== undefined || !args[index + 1]) throw new Error(usage);
      staging = announcementIdSchema.parse(args[++index]);
    } else if (arg.startsWith("--") || sourceDirectory !== undefined) {
      throw new Error(usage);
    } else {
      sourceDirectory = arg;
    }
  }
  return { sourceDirectory: sourceDirectory ?? (staging ? "announcements/examples/staging" : "announcements"), staging, publish: mode === "publish" };
}

export async function prepareAnnouncementPublish(sourceDirectory: string, staging?: string, hostPrefix?: string) {
  const prefix = announcementPublishPrefix(staging, hostPrefix);
  const source = path.resolve(sourceDirectory);
  if (!(await lstat(source)).isDirectory()) throw new Error("Source must be a real directory");
  const manifestPath = path.join(source, "current.json");

View on GitHub (pinned to 3f1d897a7c)