can1357/oh-my-pi · error · Error

Invalid EPUB: missing content.opf

Error message

Invalid EPUB: missing content.opf

What it means

The converter resolved an opf path from container.xml but archiveEntryText found no entry at that path inside the ZIP. This means the package document (content.opf) referenced by the container is absent, so metadata and spine cannot be read and convert() aborts.

Source

Thrown at packages/coding-agent/src/markit/converters/epub.ts:72

	async convert(input: Buffer, _streamInfo: StreamInfo): Promise<ConversionResult> {
		const entries = await readArchiveEntries({ bytes: input, format: "zip" });
		const parser = new XMLParser({
			ignoreAttributes: false,
			attributeNamePrefix: "@_",
			textNodeName: "#text",
			processEntities: { maxTotalExpansions: 1_000_000 },
		});
		// Find content.opf path from container.xml
		const containerXml = archiveEntryText(entries, "META-INF/container.xml");
		if (!containerXml) throw new Error("Invalid EPUB: missing container.xml");
		const container = parser.parse(containerXml) as ContainerDoc;
		const rootfile = container.container?.rootfiles?.rootfile;
		const opfPath = Array.isArray(rootfile) ? rootfile[0]["@_full-path"] : rootfile?.["@_full-path"];
		if (!opfPath) throw new Error("Invalid EPUB: missing rootfile path");
		// Parse content.opf
		const opfXml = archiveEntryText(entries, opfPath);
		if (!opfXml) throw new Error("Invalid EPUB: missing content.opf");
		const opf = parser.parse(opfXml) as OpfDoc;
		// Extract metadata
		const meta: Metadata = opf.package?.metadata ?? {};
		const metadata: Record<string, string | undefined> = {
			title: this.getText(meta["dc:title"]),
			authors: this.getTextArray(meta["dc:creator"]).join(", ") || undefined,
			language: this.getText(meta["dc:language"]),
			publisher: this.getText(meta["dc:publisher"]),
			date: this.getText(meta["dc:date"]),
			description: this.getText(meta["dc:description"]),
		};
		// Build manifest map (id → href)
		const manifestItems = opf.package?.manifest?.item;
		const itemList = Array.isArray(manifestItems) ? manifestItems : manifestItems ? [manifestItems] : [];
		const manifest = new Map<string, string>();
		for (const item of itemList) {
			manifest.set(item["@_id"], item["@_href"]);
		}

View on GitHub (pinned to 9690622007)

Solutions

  1. Compare the full-path in META-INF/container.xml against actual entry names (unzip -l); fix the path or rename the entry to match exactly (case-sensitive).
  2. Rebuild the EPUB with a compliant packer (epubcheck-validated).
  3. Re-download the EPUB; the archive is likely corrupt or partially transferred.
  4. If repacking manually, include the OPF at the exact path the container references.

Example fix

// before (container.xml says OEBPS/content.opf, archive has OEBPS/Content.opf)
// after: rename entry to match
zip book.epub OEBPS/content.opf  # exact case as full-path
Defensive patterns

Strategy: validation

Validate before calling

async function opfEntryExists(path: string, listEntries: (p: string) => Promise<string[]>, readEntry: (p: string, name: string) => Promise<string>): Promise<boolean> {
  const containerXml = await readEntry(path, "META-INF/container.xml");
  const full = /full-path="([^"]+)"/.exec(containerXml ?? "")?.[1];
  if (!full) return false;
  return (await listEntries(path)).includes(full);
}

Try / catch

try {
  const result = await markit.convertFile("book.epub");
} catch (err) {
  if (err instanceof Error && err.message === "Invalid EPUB: missing content.opf") {
    throw new Error("book.epub references an OPF that is absent from the archive; fix full-path or repack");
  }
  throw err;
}

Prevention

When it happens

Trigger: container.xml's rootfile full-path names a file (e.g. OEBPS/content.opf) that does not exist in the archive — wrong path, case mismatch, or the OPF was deleted.

Common situations: EPUB re-zipped across case-sensitive/insensitive filesystems (Content.opf vs content.opf); partial extraction/repack dropped the OPF; custom directory layout with a stale container.xml pointing at an old OPF location.

Related errors


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