can1357/oh-my-pi · error · Error

archive destination exists: ${destSession}

Error message

archive destination exists: ${destSession}

What it means

moveSessionWithArtifacts archives a session file by gzipping it to a destination path. Before any move it checks that neither the destination nor its legacy (.gz-less or .gz-suffixed) counterpart exists, throwing this error to avoid silently overwriting an existing archive.

Source

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

		if (renamed) await fs.rm(destination, { force: true });
		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);

View on GitHub (pinned to 9690622007)

Solutions

  1. Inspect the destination path in the message; delete or rename the stale archive if it is no longer needed, then re-run GC
  2. Ensure only one GC process runs at a time (the GC lock should enforce this — check for stale locks)
  3. If the collision is systematic, fix the destination-naming logic so archived sessions get unique destinations

Example fix

// before
mv session.jsonl session.jsonl.gz  // fails if destination exists
// after
rm stale-session.jsonl.gz && omp gc  // clear stale archive, then retry
Defensive patterns

Strategy: validation

Validate before calling

import * as fs from "node:fs/promises";
const dest = computeDestinationPath(sessionPath);
for (const p of [dest, dest.replace(/\.gz$/, "")]) {
  try { await fs.access(p); throw new Error(`destination exists: ${p}`); }
  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:")) {
    const dest = err.message.split("archive destination exists: ")[1];
    // inspect/resolve the collision (delete stale archive or pick a new destination) before retrying
  } else throw err;
}

Prevention

When it happens

Trigger: Archiving a session when candidate.destinationPath already exists on disk — e.g. re-running GC after a previous partial archive, or two sessions resolving to the same destination name.

Common situations: Repeated `omp gc`/stats-cleanup runs where a prior run crashed after writing the destination; session files renamed/copied so their computed destination collides; concurrent GC invocations racing on the same session.

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/80e9ca635c9c51ad. Report an issue: GitHub.