MemberJunction / MemberJunction/MJ
Open App: the private-repo 404 misattribution fixed in #4505 remains on `mj app upgrade`, `check-updates`, and in the credential message itself
- Dominant language
- TSQL
- Stars
- 29
- Forks
- 6
- Avg merge
- 1d 8h
- Merged PRs (30d)
- 308
Description
## Summary
#4505 fixed the private-repo 404 misattribution on `mj app install`'s two call sites — tag validation and manifest fetch. Three instances of the same class remain, found during that PR's review. None is reachable through `mj app install`, which is why they were left out of that change rather than fixed there.
The root fact is unchanged: **GitHub returns 404, not 403, for a repository it will not show you**, so "this thing inside the repo is missing" and "this repo is invisible to you" are indistinguishable on any request for something inside it. `packages/OpenApp/Engine/src/github/github-client.ts` now has a module-private `DescribeNotFound` helper that probes `GET /repos/{owner}/{repo}` to tell them apart.
## 1. `mj app upgrade` and `mj app check-updates` still misattribute it
The list paths swallow a 404 into an empty array (`github-client.ts`, the `catch` blocks in `ListGitHubReleases` and `ListGitHubTags` — both call `ThrowIfRateLimitedOrForbidden` for 403/429, then `return []`). For a private repo with no credential:
- **`mj app upgrade `** with no `--version` → `GetLatestVersion` returns `null` → `install-orchestrator.ts` reports **"Could not determine target version"**. A dead end that says nothing about the cause.
- **`mj app check-updates`** → `update-check.ts` reports **"no version tags or releases found in \"** — the same misattribution in the same shape: it blames the repo's *contents* for the repo's *invisibility*. Milder than the upgrade case because it is categorised `Unresolved` rather than folded into the healthy line.
This is the same user, in the same session, one command away from the one that was fixed.
Note this needs a different shape of fix from #4505: those paths return `[]` rather than composing an error message, so they cannot simply call `DescribeNotFound`. The `GitHubAccessError` precedent (already used there for 403/429) is probably the closer model.
## 2. The message never says *which* credential it used
There are three credential sources and they have a precedence:
```
openApps.github.tokens (per-repo map, URL-keyed) highest
openApps.github.token (global, in mj.config.cjs)
GITHUB_TOKEN (environment) lowest
```
`buildGitHubOptions` (`packages/MJCLI/src/utils/open-app-context.ts`) resolves `config.openApps?.github?.token ?? process.env.GITHUB_TOKEN` before the engine sees anything, so a config `token` silently beats the env var; `ResolveToken` then checks the per-repo map first.
The credential-supplied branch of `UnreadableRepoMessage` says *"the GitHub credential supplied does not grant access"*. A maintainer with a stale `openApps.github.token` in `mj.config.cjs` reads that, exports a fresh `GITHUB_TOKEN`, gets the byte-identical message, and is stuck — while believing the message refers to the token they just set.
Having `ResolveToken` (or a sibling) report *which source* the token came from, and naming it in the message, would close the last gap in "name the right remedy".
## 3. `'repo' scope` is classic-PAT vocabulary
`UnreadableRepoMessage` tells the caller to check the token "carries `'repo'` scope". That is correct for a classic PAT only. A **fine-grained** PAT without the repository selected also returns 404, and its remedy is repository access plus `Contents: Read`. An **SSO-enforced org** additionally requires the token be authorized for that org. One extra clause would cover all three without lengthening the message much.
## 4. `DownloadMigrations` against a divergent `manifest.repository`
`DownloadMigrations` was deliberately left out of #4505 because by the time migrations download, the manifest fetch has already proved the repo readable. That holds for the common case — but it is called with `manifest.repository`, not `options.Source`. When a manifest declares a *different* repository and that one is private, a 404 surfaces as a bare `Failed to download migrations: Not Found`.
Rare, but it is the same class and the same fix shape as #4505's two sites.
## Context
Found during the review of the #4505 fix. Items 1 and 4 are behaviour a user can hit today; items 2 and 3 are message-quality gaps in the new text that PR added.
Contributor guide
Research direction
Start with packages/OpenApp/Engine/src/github/github-client.ts, especially ListGitHubReleases and ListGitHubTags, then trace GetLatestVersion, install-orchestrator.ts, and update-check.ts. Read the credential flow in packages/MJCLI/src/utils/open-app-context.ts and the existing UnreadableRepoMessage and ResolveToken logic. Done means inaccessible repositories are distinguished consistently, credential source and access guidance are accurate, and divergent manifest repositories no longer produce a bare 404.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- github, typescript
- Domain
- api, authentication, cli
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 58/100