different-ai/openwork · error · ApiError

plugin_ref_not_found

plugin_ref_not_found

Error message

Could not resolve the requested branch or tag

What it means

resolveRefAndTree resolves the requested ref (branch or tag) against a list of candidate refs, attempting each one; if every candidate fails, it rethrows the last error, and only when there was no Error at all does it throw this 404 ApiError. It signals that the requested branch or tag could not be resolved to a commit tree for the repository.

Source

Thrown at apps/server/src/claude-plugin-bundle.ts:197

        ref: source.treeSegments.slice(0, index).join("/"),
        dir: index < source.treeSegments.length ? source.treeSegments.slice(index).join("/") : null,
      });
    }
  } else {
    candidates.push({ ref: await resolveDefaultBranch(source), dir: null });
  }

  let lastError: unknown = null;
  for (const candidate of candidates) {
    try {
      const tree = await fetchRepoTree(source, candidate.ref);
      return { ref: candidate.ref, dir: candidate.dir, tree };
    } catch (error) {
      lastError = error;
    }
  }
  if (lastError instanceof Error) throw lastError;
  throw new ApiError(404, "plugin_ref_not_found", "Could not resolve the requested branch or tag");
}

// Find the plugin root: the given subdir, the repo root, or the shallowest
// directory containing `.claude-plugin/plugin.json`.
function locatePluginRoot(tree: TreeEntry[], dir: string | null): string {
  const manifestPaths = tree
    .map((entry) => entry.path)
    .filter((path) => path === ".claude-plugin/plugin.json" || path.endsWith("/.claude-plugin/plugin.json"));
  if (dir) {
    const normalized = dir.replace(/\/+$/, "");
    const expected = `${normalized}/.claude-plugin/plugin.json`;
    if (!manifestPaths.includes(expected)) {
      throw new ApiError(404, "plugin_manifest_not_found", `No .claude-plugin/plugin.json found under ${normalized}/`);
    }
    return `${normalized}/`;
  }
  if (manifestPaths.length === 0) {
    throw new ApiError(404, "plugin_manifest_not_found", "No .claude-plugin/plugin.json found in this repository");

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Check the ref in the URL against the repository's actual branches/tags on GitHub.
  2. Use a commit SHA or an existing tag instead of a branch name for reproducible installs.
  3. If relying on the default branch, confirm the repo's default branch name (main vs master).
  4. Inspect the wrapped lastError (if a prior error was rethrown) to distinguish network/403 issues from a truly unknown ref.

Example fix

// before
GET /plugin/tree/mian/<dir>          // typo, no such branch
// after
GET /plugin/tree/main/<dir>          // or /tree/v1.2.0/<dir> for a tag
Defensive patterns

Strategy: validation

Validate before calling

const ref = "v1.2.0"; // resolve beforehand
const exists = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/ref/tags/${ref}`).then(r => r.ok);
if (!exists) throw new Error(`ref ${ref} not found`);

Try / catch

try {
  await installPlugin(url);
} catch (e) {
  if (isApiError(e) && e.code === "plugin_ref_not_found") {
    // fall back to the repo default branch
  }
}

Prevention

When it happens

Trigger: Requesting a plugin bundle with a ref (branch/tag) that does not exist in the repository, or an empty/unrecognized ref string, such that no candidate resolved and no underlying Error was captured.

Common situations: Default branch renamed from 'main' to 'master' or vice versa; tag deleted or not yet created; typo like 'tree/mainn'; repo where all candidate refs (main/master/HEAD) fail to resolve.

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/b3d240292570578c. Report an issue: GitHub.