paperclipai/paperclip · error

--rollback is only available for managed installs.

Error message

--rollback is only available for managed installs.

What it means

Thrown by updateCommand when the user passes --rollback but detectInstallMode does not report 'managed'. Rollback only manipulates the managed install store (current symlink + manifest previous[]), so it is meaningless for global npm installs, ephemeral npx runs, source checkouts, or unrecognized modes. The error fires before any filesystem mutation.

Source

Thrown at cli/src/commands/update.ts:174

  try {
    await restartActiveService(rolledBack.version);
  } catch (restartError) {
    throw new Error(
      `${payloadLabel} failed service validation and was rolled back to ${rolledBack.version}, but the rolled-back service also failed to restart.`,
      { cause: new AggregateError([validationError, restartError]) },
    );
  }
  throw new Error(`${payloadLabel} failed service validation and was rolled back to ${rolledBack.version}.`, { cause: validationError });
}

export async function updateCommand(options: UpdateOptions, overrides: Partial<Dependencies> = {}): Promise<void> {
  const paths = overrides.paths ?? resolveInstallStorePaths();
  const executablePath = overrides.executablePath ?? process.argv[1] ?? "";
  const runCommand = overrides.runCommand ?? execFileAsync;
  const mode = detectInstallMode(executablePath, paths);
  const manifest = readInstallManifest(paths);
  if (options.rollback) {
    if (mode !== "managed") throw new Error("--rollback is only available for managed installs.");
    if (options.dryRun) { emit(options, { mode, action: "rollback", dryRun: true, target: manifest?.previous[0]?.version ?? null }, `Would roll back to ${manifest?.previous[0]?.version ?? "the previous payload"}.`); return; }
    const next = await withInstallStoreLock(async () => rollbackManagedInstall(paths), paths);
    const restarted = await (overrides.restartActiveService ?? restartActiveManagedService)(next.version);
    emit(options, { mode, action: "rollback", version: next.version, restarted }, pc.green(`Rolled back to paperclipai ${next.version}${restarted ? " and restarted the active service" : ""}. Database migrations are not reversed; restore the pre-update backup if needed.`));
    return;
  }
  if (mode === "npx") { emit(options, { mode, action: "install" }, "This is an ephemeral npx install. Run `paperclipai install`, then use `paperclipai update` from the managed shim."); return; }
  if (mode === "source" || mode === "unknown") { emit(options, { mode, action: "manual" }, "This appears to be a source checkout. Update it with `git pull` followed by `pnpm install`; Paperclip will not mutate the repository."); return; }
  const request = resolveUpdateRequest(mode === "managed" ? manifest : null, options);
  if (mode === "managed" && manifest?.source === "git") {
    if (!manifest.repo || !manifest.ref || !manifest.sha) throw new Error("Managed git install metadata is incomplete.");
    if (/^[0-9a-f]{7,40}$/i.test(manifest.ref)) { emit(options, { mode, source: "git", pinned: true, sha: manifest.sha }, `Git install is pinned at ${manifest.sha.slice(0, 12)}.`); return; }
    const targetSha = await resolveGitHubRef(manifest.repo, manifest.ref, runCommand);
    if (targetSha === manifest.sha) { emit(options, { mode, source: "git", changed: false, sha: targetSha, ref: manifest.ref }, `${manifest.repo}@${manifest.ref} is already at ${targetSha.slice(0, 12)}.`); return; }
    if (options.check || options.dryRun) { emit(options, { mode, source: "git", changed: true, currentSha: manifest.sha, targetSha, ref: manifest.ref, dryRun: Boolean(options.dryRun) }, `Git update available: ${manifest.sha.slice(0, 12)} → ${targetSha.slice(0, 12)}.`); if (options.check) process.exitCode = 10; return; }
    if (options.yes !== true) {
      const confirmed = await (overrides.confirm ?? defaultConfirm)(`Update from ${manifest.repo}@${manifest.ref} and execute build scripts from commit ${targetSha.slice(0, 12)}?`);
      if (!confirmed) throw new Error("Git update cancelled. Re-run with --yes to confirm executing build scripts from the updated commit.");

View on GitHub (pinned to 67001ec6eb)

Solutions

  1. Switch to a managed install first: `paperclipai install`, then `paperclipai update --rollback`.
  2. For global-npm, downgrade explicitly with `paperclipai update --version <older>` instead of --rollback.
  3. For npx, the install is ephemeral — reinstall the desired version directly.
  4. For source checkouts, use `git checkout <tag>` to change versions.

Example fix

# before (globally installed)
paperclipai update --rollback
# after
paperclipai update --version 1.2.3
Defensive patterns

Strategy: validation

Validate before calling

import { detectInstallMode, resolveInstallStorePaths } from '../commands/update.js';
function canRollback(): boolean {
  return detectInstallMode(process.argv[1] ?? '', resolveInstallStorePaths()) === 'managed';
}
// if (!canRollback()) { /* use update --version or install managed first */ }

Try / catch

try {
  await updateCommand({ rollback: true });
} catch (error) {
  if (error instanceof Error && error.message === '--rollback is only available for managed installs.') {
    // fall back to a direct version install for non-managed modes
    await updateCommand({ version: desiredVersion, yes: true });
  } else throw error;
}

Prevention

When it happens

Trigger: Running `paperclipai update --rollback` from an npx invocation, a `npm install -g paperclipai` global install, a git source checkout, or any path detectInstallMode classifies as 'npx', 'global-npm', 'source', or 'unknown'.

Common situations: User installed via npm globally and expects rollback to work. Running the CLI via `npx paperclipai update --rollback`. Running from a cloned repo. PATH pointing at a non-managed binary.

Related errors


AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12). Data as JSON: /api/errors/3ce20eeda535e337. Report an issue: GitHub.