can1357/oh-my-pi · error · Error

archive destination exists: ${legacyDestSession}

Error message

archive destination exists: ${legacyDestSession}

What it means

The legacy-destination counterpart of the archive collision check: when the computed destination is `X.gz`, the code also guards the uncompressed `X` (and vice versa) so a leftover archive from an older naming scheme still blocks the move instead of being overwritten.

Source

Thrown at packages/coding-agent/src/cli/gc-cli.ts:527

		throw error;
	}
}

async function restoreGzipSessionFile(source: string, destination: string): Promise<void> {
	await fs.mkdir(path.dirname(destination), { recursive: true });
	const decompressed = gunzipSync(await Bun.file(source).bytes());
	await Bun.write(destination, decompressed);
	await fs.unlink(source);
}

async function moveSessionWithArtifacts(candidate: ArchiveCandidate): Promise<void> {
	const sourceSession = candidate.session.path;
	const destSession = candidate.destinationPath;
	const legacyDestSession = destSession.endsWith(".gz") ? destSession.slice(0, -".gz".length) : `${destSession}.gz`;
	const sourceArtifacts = sessionArtifactsPath(sourceSession);
	const destArtifacts = sessionArtifactsPath(destSession);
	if (await pathExists(destSession)) throw new Error(`archive destination exists: ${destSession}`);
	if (await pathExists(legacyDestSession)) throw new Error(`archive destination exists: ${legacyDestSession}`);
	if ((await pathExists(sourceArtifacts)) && (await pathExists(destArtifacts))) {
		throw new Error(`archive artifacts destination exists: ${destArtifacts}`);
	}

	const moved: Array<{ source: string; destination: string; compressed?: boolean }> = [];
	try {
		await gzipSessionFile(sourceSession, destSession);
		moved.push({ source: sourceSession, destination: destSession, compressed: true });
		if (await pathExists(sourceArtifacts)) {
			await movePath(sourceArtifacts, destArtifacts);
			moved.push({ source: sourceArtifacts, destination: destArtifacts });
		}
	} catch (error) {
		for (const move of moved.reverse()) {
			try {
				if (move.compressed) {
					await restoreGzipSessionFile(move.destination, move.source);
				} else {

View on GitHub (pinned to 9690622007)

Solutions

  1. Inspect the legacy path in the message; delete or move the stale file if it is redundant
  2. Verify the existing archive is intact/needed before deleting (it may be the previous successful archive of the same session)
  3. Re-run GC after clearing the collision
Defensive patterns

Strategy: validation

Validate before calling

import * as fs from "node:fs/promises";
const legacy = dest.endsWith(".gz") ? dest.slice(0, -3) : `${dest}.gz`;
try { await fs.access(legacy); throw new Error(`legacy destination exists: ${legacy}`); }
catch (e) { if ((e as NodeJS.ErrnoException).code !== "ENOENT") throw e; }

Try / catch

try {
  await archiveSession(candidate);
} catch (err) {
  if (err instanceof Error && err.message.startsWith("archive destination exists:")) {
    // check both dest and legacy path from the message; remove or relocate stale files, then retry
  } else throw err;
}

Prevention

When it happens

Trigger: Archiving a session when the legacy path (destination with the .gz suffix stripped, or appended) already exists — typically left behind by a previous GC run that used the older naming convention or crashed midway.

Common situations: Upgraded omp versions changing archive naming; a previous run interrupted between gzip and cleanup; manual decompression of an old archive recreating the uncompressed file at the legacy path.

Understand the failure class

Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/d7e3c193e54a4d88. Report an issue: GitHub.