Yeachan-Heo/oh-my-codex · error

manifest_duplicate_logical_key

manifest_duplicate_logical_key

Error message

[native-assets] manifest_duplicate_logical_key: ${asset.archive}

What it means

Two assets in the manifest map to the same logical key (product/platform/target identity), meaning the same logical asset is declared twice. The validator enforces uniqueness via nativeReleaseAssetLogicalKey.

Solutions

  1. Remove the duplicate entry so each logical asset appears once
  2. If you need multiple archives per platform, restructure to distinct product/target values
  3. Diff the manifest against the last known-good version to spot the copied block

Example fix

// before
[{platform:'linux-x64',archive:'a.tar.zst'},{platform:'linux-x64',archive:'b.tar.zst'}]
// after
[{platform:'linux-x64',archive:'a.tar.zst'},{platform:'linux-arm64',archive:'b.tar.zst'}]
Defensive patterns

Strategy: validation

Validate before calling

const keys = new Set<string>();
for (const a of manifest.assets) {
  const k = nativeReleaseAssetLogicalKey(a);
  if (keys.has(k)) throw new Error(`duplicate logical key: ${k}`);
  keys.add(k);
}

Prevention

When it happens

Trigger: Two entries with identical product+platform (or whatever fields compose the logical key), e.g. the same linux-x64 asset listed with two different archive names.

Common situations: Copy-pasted asset blocks when adding a new platform, or a partial edit that duplicated an existing row.

Related errors


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

Appendix: source

Thrown at src/native-assets/policy.ts:170

    }
    const mapping = nativeTargetMapping(asset.target);
    if (!mapping || mapping.platform !== asset.platform || mapping.arch !== asset.arch
      || (mapping.platform === 'linux' ? asset.libc !== mapping.libc : asset.libc !== undefined)) {
      throw policyError('manifest_target_mismatch', asset.target || asset.archive);
    }
    const expectedBinary = nativeProductBinaryName(asset.product);
    const expectedBinaryPath = nativeProductBinaryPath(asset.product, asset.platform);
    if (!expectedBinary || !expectedBinaryPath || asset.binary !== expectedBinary) {
      throw policyError('manifest_binary_product_mismatch', asset.binary);
    }

    const binaryPath = normalizeNativeArchivePath(asset.binary_path, 'file');
    if (binaryPath !== asset.binary_path || binaryPath.split('/').at(-1) !== expectedBinaryPath) {
      throw policyError('manifest_binary_basename_mismatch', asset.binary_path);
    }

    const key = nativeReleaseAssetLogicalKey(asset);
    if (logicalKeys.has(key)) throw policyError('manifest_duplicate_logical_key', asset.archive);
    logicalKeys.add(key);
    const basename = nativeReleaseAssetBasename(asset);
    if (asset.archive !== basename || !nativeArchiveSuffix(basename)) {
      throw policyError('manifest_archive_invalid', asset.archive);
    }
    if (archiveHintMismatch(asset, basename)) throw policyError('manifest_archive_hint_mismatch', basename);
    if (basenames.has(basename)) throw policyError('manifest_duplicate_archive_basename', basename);
    basenames.add(basename);
    let downloadUrl: URL;
    try {
      downloadUrl = new URL(asset.download_url);
    } catch {
      throw policyError('manifest_invalid_url', asset.download_url);
    }
    if ((downloadUrl.protocol !== 'https:' && downloadUrl.protocol !== 'http:')
      || downloadUrl.pathname.split('/').at(-1) !== basename) {
      throw policyError('manifest_url_basename_mismatch', asset.download_url);
    }

View on GitHub (pinned to 3ad79a8a6f)