can1357/oh-my-pi · error · Error

archive artifacts destination exists: ${destArtifacts}

Error message

archive artifacts destination exists: ${destArtifacts}

What it means

When both the source session has an artifacts directory and the destination artifacts path already exists, the move is refused with this error — archiving would merge or overwrite artifact data that already occupies the target location.

Source

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

}

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 {
					await movePath(move.destination, move.source);
				}

View on GitHub (pinned to 9690622007)

Solutions

  1. Inspect destArtifacts in the message; remove or relocate the stale destination artifacts directory if unneeded
  2. Verify whether the artifacts belong to the same session (compare contents/ids) before deleting
  3. Re-run GC once the destination artifacts path is clear
Defensive patterns

Strategy: validation

Validate before calling

import * as fs from "node:fs/promises";
const artifacts = sessionArtifactsPath(dest);
try { await fs.access(artifacts); throw new Error(`artifacts destination exists: ${artifacts}`); }
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 artifacts destination exists:")) {
    const destArtifacts = err.message.split("exists: ")[1];
    // compare/remove the stale destination artifacts directory, then retry
  } else throw err;
}

Prevention

When it happens

Trigger: Archiving a session whose artifacts directory exists while a destination artifacts directory also exists — e.g. the session was partially archived before, or two sessions share an artifacts location.

Common situations: Partial archive from a crashed earlier run (session file moved back but artifacts left); duplicate session ids after copy/import; concurrent GC processes targeting the same destination.

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/1c013080a8e30520. Report an issue: GitHub.