paperclipai/paperclip · error
Choose only one of --latest, --canary, or --version.
Error message
Choose only one of --latest, --canary, or --version.
What it means
Thrown by resolveUpdateRequest when more than one of the mutually-exclusive channel selectors --latest, --canary, and --version is supplied at once. The update command needs a single unambiguous target channel; passing two or more makes the intent indeterminate. The check sums the boolean count of the three flags and rejects when the total exceeds one.
Source
Thrown at cli/src/commands/update.ts:119
const bParts = b.prerelease.split(".");
for (let index = 0; index < Math.max(aParts.length, bParts.length); index += 1) {
const leftPart = aParts[index];
const rightPart = bParts[index];
if (leftPart === undefined) return -1;
if (rightPart === undefined) return 1;
if (leftPart === rightPart) continue;
const leftNumeric = /^\d+$/.test(leftPart);
const rightNumeric = /^\d+$/.test(rightPart);
if (leftNumeric && rightNumeric) return Math.sign(Number(leftPart) - Number(rightPart));
if (leftNumeric !== rightNumeric) return leftNumeric ? -1 : 1;
return leftPart < rightPart ? -1 : 1;
}
return 0;
}
export function resolveUpdateRequest(manifest: InstallManifest | null, options: Pick<UpdateOptions, "canary" | "latest" | "version">): { spec: string; channel: InstallChannel; explicit: boolean } {
const selected = Number(Boolean(options.canary)) + Number(Boolean(options.latest)) + Number(Boolean(options.version));
if (selected > 1) throw new Error("Choose only one of --latest, --canary, or --version.");
if (options.version) return { spec: options.version.trim(), channel: "pinned", explicit: true };
if (options.canary) return { spec: "canary", channel: "canary", explicit: true };
if (options.latest) return { spec: "latest", channel: "latest", explicit: true };
if (manifest?.channel === "pinned") return { spec: manifest.version, channel: "pinned", explicit: false };
const channel = manifest?.channel === "canary" ? "canary" : "latest";
return { spec: channel, channel, explicit: false };
}
export function rollbackManagedInstall(paths = resolveInstallStorePaths()): InstallManifest {
const manifest = readInstallManifest(paths);
if (!manifest) throw new Error("No managed install was found to roll back.");
const target = manifest.previous[0];
if (!target) throw new Error("No previous managed payload is available for rollback.");
if (!fs.existsSync(target.payloadPath)) throw new Error(`Previous payload is missing: ${target.payloadPath}`);
const current: InstallRecord = { source: manifest.source, version: manifest.version, channel: manifest.channel, payloadPath: manifest.payloadPath, repo: manifest.repo, ref: manifest.ref, sha: manifest.sha, installedAt: manifest.installedAt };
const next: InstallManifest = { schemaVersion: manifest.schemaVersion, ...target, previous: [current, ...manifest.previous.slice(1)].slice(0, 2) };
const oldTarget = fs.readlinkSync(paths.currentPath);
flipCurrentAtomic(target.payloadPath, paths);View on GitHub (pinned to 67001ec6eb)
Solutions
- Pass exactly one channel flag: `--latest`, `--canary`, or `--version <semver>`.
- Drop all three to use the manifest's recorded channel (auto-detected from the prior install).
- Audit your shell alias / wrapper script and remove the conflicting flag.
Example fix
# before paperclipai update --latest --version 1.2.3 # after paperclipai update --version 1.2.3
Defensive patterns
Strategy: validation
Validate before calling
// Reject conflicting channel flags before calling resolveUpdateRequest
function assertSingleUpdateChannel(options: { canary?: boolean; latest?: boolean; version?: string }): void {
const n = Number(Boolean(options.canary)) + Number(Boolean(options.latest)) + Number(Boolean(options.version));
if (n > 1) throw new Error('Pass only one of --latest, --canary, --version');
}
// assertSingleUpdateChannel(options); resolveUpdateRequest(manifest, options); Try / catch
try {
resolveUpdateRequest(manifest, options);
} catch (error) {
if (error instanceof Error && error.message === 'Choose only one of --latest, --canary, or --version.') {
// pick one channel by priority and retry
const fixed = { version: options.version, canary: !options.version && options.canary, latest: !options.version && !options.canary && options.latest };
resolveUpdateRequest(manifest, fixed);
} else throw error;
} Prevention
- In wrapper scripts, derive the channel from a single variable rather than concatenating flags.
- Use a CLI arg parser that declares the three flags in a mutually-exclusive group.
- Document the precedence so users do not stack --latest with --version.
When it happens
Trigger: Invoking `paperclipai update --latest --canary`, `--latest --version 1.2.3`, `--canary --version 1.2.3`, or all three together. Also triggered by programmatic callers of updateCommand/resolveUpdateRequest passing an UpdateOptions object with two or more of those fields truthy.
Common situations: Copy-pasting flags from different docs/tutorials. Scripted invocations concatenating flags conditionally without mutual exclusion. Shell aliases that bake in --latest combined with a manual --version on the command line.
Related errors
- --payload must be a JSON object
- ${name} must be a JSON object
- Invalid ${name} JSON: ${err instanceof Error ? err.message :
- --file is required
- Environment variable ${envName} is empty or not set.
AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12).
Data as JSON: /api/errors/e3ec86c82fa68a13.
Report an issue: GitHub.