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
- 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)
- Ensure no directory currently occupies the workspace's restore location before unarchiving
- 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
- Ensure the restore location is free before unarchiving
- Keep the archive manifest in sync across machines to avoid stale entries
- Surface res.err by catching early rather than letting it collapse to the generic message
- Verify the workspace was actually found (the API returns false when not) before interpreting the throw as an unarchive failure
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
- workspace is still being created and cannot be
- no such folder
- folder name must not be empty
- no such folder
- workspace has no agent process to stop
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)