Yeachan-Heo/oh-my-codex · critical · Error

native_agent_canonical_invalid

native_agent_canonical_invalid

Error message

native_agent_canonical_invalid
agent=${agent.name}
message=alias/merged native agents must declare a canonical target

What it means

Thrown when the member entry to write exists but reports size zero, so the library refuses to hydrate it. An empty binary written to disk would create a broken executable, so validation happens before any file I/O.

Source

Thrown at src/agents/policy.ts:69

  promptName: string,
  manifest: Pick<CatalogManifest, "agents">,
): boolean {
  return (
    manifest.agents.some((agent) => agent.name === promptName) ||
    NON_NATIVE_AGENT_PROMPT_ASSETS.has(promptName)
  );
}

export function assertNativeAgentCanonicalTargets(
  manifest: Pick<CatalogManifest, "agents">,
): void {
  const byName = getCatalogAgentByName(manifest);

  for (const agent of manifest.agents) {
    if (agent.status !== "alias" && agent.status !== "merged") continue;

    if (!agent.canonical) {
      throw new Error(
        [
          "native_agent_canonical_invalid",
          `agent=${agent.name}`,
          "message=alias/merged native agents must declare a canonical target",
        ].join("\n"),
      );
    }

    const canonical = byName.get(agent.canonical);
    if (!canonical) {
      throw new Error(
        [
          "native_agent_canonical_invalid",
          `agent=${agent.name}`,
          `canonical=${agent.canonical}`,
          "message=canonical native agent target is not listed in the catalog",
        ].join("\n"),
      );

View on GitHub (pinned to 3ad79a8a6f)

Solutions

  1. Re-download the archive and verify checksum/size against the release metadata
  2. Confirm with unzip -l / tar -tvf that the entry size is non-zero; if not, the upstream asset is broken — rebuild it
  3. Clear any intermediary cache (npm cache, CI cache, mirror) that may hold the truncated copy
  4. Audit the release pipeline so binaries are fully built before archiving

Example fix

// before
await writeSelectedNativeArchiveMember(archive, 'mytool', dest); // throws archive_binary_empty

// after
const entries = await inspectNativeArchive(archive);
const e = entries.find(c => c.normalizedName === 'mytool');
if (!e || e.size <= 0) await refetchArchive();
await writeSelectedNativeArchiveMember(archive, 'mytool', dest);
Defensive patterns

Strategy: validation

Validate before calling

const entries = await inspectNativeArchive(archivePath);
const e = entries.find(c => c.type === 'file' && c.normalizedName === member);
if (!e || e.size <= 0) throw new Error('refusing to hydrate empty binary member');

Type guard

const isHydratable = (e: { type: string; size: number }): boolean => e.type === 'file' && e.size > 0;

Try / catch

try { await writeSelectedNativeArchiveMember(p, m, d); } catch (e) { if ((e as {code?:string}).code === 'archive_binary_empty') { await rm(p, {force:true}); /* re-fetch asset, retry once */ } else throw e; }

Prevention

When it happens

Trigger: writeSelectedNativeArchiveMember found the entry but selected.size <= 0 at the pre-write check. Same root causes as other empty-entry errors: truncated archive, placeholder file, or corrupt size header.

Common situations: Disk-full during CI artifact packaging producing empty entries; partially-synced mirrors or caches; LFS pointers not fetched; antivirus/quarantine tools zeroing out binaries inside archives on shared storage.

Related errors


AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27). Data as JSON: /api/errors/3431107d24eb0c82. Report an issue: GitHub.