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
- Check the ref in the URL against the repository's actual branches/tags on GitHub.
- Use a commit SHA or an existing tag instead of a branch name for reproducible installs.
- If relying on the default branch, confirm the repo's default branch name (main vs master).
- 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
- Use tags or commit SHAs rather than mutable branch names.
- Confirm the branch/tag exists on GitHub before constructing the URL.
- Remember the default branch may be master, not main.
- Handle repo default-branch renames in automation.
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
- plugin_manifest_not_found
- plugin_ambiguous
- Failed to start GitHub install (${response.status}).
- Failed to complete GitHub installation (${response.status}).
- Failed to create the imported plugin.
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/b3d240292570578c.
Report an issue: GitHub.