sinelaw/fresh · error

unarchive failed

Error message

unarchive failed

What it means

The unarchive-by-root API locates the archived workspace matching a wanted repo root and calls unarchiveOne. If the unarchive operation reports failure (res.ok falsy), it throws res.err when a reason is provided, otherwise the generic 'unarchive failed'. This means the workspace was found but the unarchive itself did not complete.

Solutions

  1. Read res.err / the thrown message for the specific cause and fix it (e.g. move the existing directory at the restore path out of the way)
  2. Ensure no directory currently occupies the workspace's restore location before unarchiving
  3. Retry after resolving manifest conflicts, letting triggerSyncAsync push the corrected manifest

Example fix

// before
await api.unarchiveByRoot(repoRoot);
// after
try {
  await api.unarchiveByRoot(repoRoot);
} catch (e) {
  if (fs.existsSync(expectedPath)) {
    fs.renameSync(expectedPath, expectedPath + ".bak");
    await api.unarchiveByRoot(repoRoot);
  } else { throw e; }
}
Defensive patterns

Strategy: try-catch

Validate before calling

const targetPath = expectedRestorePath(repoRoot);
if (fs.existsSync(targetPath)) {
  throw new Error(`restore path already occupied: ${targetPath}`);
}

Try / catch

try {
  await api.unarchiveByRoot(repoRoot);
} catch (e) {
  const msg = String(e);
  if (msg.includes("unarchive failed") && !msg.includes("Error: unarchive failed: ")) {
    // no detail: check restore path and manifest, then retry
    clearRestorePath(repoRoot);
    await api.unarchiveByRoot(repoRoot);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling the unarchive API for a repo root that resolves to an archived workspace whose unarchive fails — target path conflicts (a directory already exists at the restore location), manifest write failures, or cross-machine sync problems.

Common situations: Restoring over an existing checkout of the same repo; disk/permission problems at the restore target; concurrent modification of the archive manifest from another machine.

Related errors


AI-assisted analysis of sinelaw/fresh@67894ca546 (2026-09-13). Data as JSON: /api/errors/6a61f80077fea12e. Report an issue: GitHub.

Appendix: source

Thrown at crates/fresh-editor/plugins/orchestrator.ts:10751

  // Match on the archived path first (unique), then the display name, which
  // is what a human reads off the listing. A name shared by two archived
  // workspaces resolves to the newest, matching the listing's own order.
  let match: ArchivedSession | null = null;
  for (const { manifest } of scanArchiveManifests()) {
    for (const e of manifest.sessions) {
      if (e.root === want) {
        match = e;
        break;
      }
      if (e.label === want && (!match || e.archived_at > match.archived_at)) {
        match = e;
      }
    }
    if (match && match.root === want) break;
  }
  if (!match) return false;
  const res = await unarchiveOne(match);
  if (!res.ok) throw new Error(res.err || "unarchive failed");
  // The manifest changed, so push it the same way the archive path does.
  if (res.repoRoot) triggerSyncAsync(res.repoRoot);
  refreshOpenDialog();
  return true;
}

function apiSetDockView(view: "card" | "compact"): void {
  if (view !== "card" && view !== "compact") {
    throw new Error(`unknown dock view: ${view}`);
  }
  dockView = view;
  // Pin it for the rest of the session, exactly as the toolbar's "view"
  // button does — the `defaultView` setting only decides where the dock
  // *starts*, so without the override a later re-open would undo this.
  dockViewOverride = view;
  refreshOpenDialog();
}

View on GitHub (pinned to 67894ca546)