Summary
Core's --git intercepts every git-smart-HTTP request globally, so a plugin cannot serve git-over-HTTP under its own prefix. isGitRequest(urlPath) matches by substring regardless of path:
// src/handlers/git.js
export function isGitRequest(urlPath) {
return urlPath.includes('/info/refs')
|| urlPath.includes('/git-upload-pack')
|| urlPath.includes('/git-receive-pack');
}
The onRequest hook and the git-handler dispatch in server.js both use it, so a request like GET /forge/<owner>/<repo>.git/info/refs?service=git-upload-pack is grabbed by core's git handler — which looks the repo up under the pod root, doesn't find it, and returns 401 — before the plugin that owns /forge ever sees it.
Consumer / proof-of-need
The out-of-tree forge plugin (JavaScriptSolidServer/plugins) hosts git repos at /forge/<owner>/<name>.git and serves smart-HTTP itself (via git-http-backend, the gitscratch/ pattern). With --git enabled on the same server, clone/push to the forge's URLs fail — core shadows them. The forge's browse / UI / anchor / nostr layers work (different paths); only its git transport is shadowed. This blocks using the forge as a real remote on any pod that also runs core --git (which serves the pod's own repos).
It's an either/or today:
--git on → pod-git works, forge-git shadowed
--git off → forge-git works, but the pod's own repos lose git-over-HTTP
You can't have both.
Proposed fix
Core's git dispatch should skip paths owned by a plugin — the loader already knows each plugin's prefix (and its api.reservePath claims). Options:
- Exclude registered plugin prefixes from the git guard (cleanest — a plugin mounted at
/forge owns /forge/*, git or not).
- Exclude
api.reservePath-claimed paths from the git guard.
- A config escape hatch, e.g.
gitExclude: ['/forge'].
Any of these lets core pod-git and plugin-served git coexist.
Workaround
A one-line local patch to isGitRequest excluding the plugin prefix (if (urlPath.startsWith('/forge/')) return false;) restores forge git while keeping pod-git — demo-grade, pending the general fix above.
Summary
Core's
--gitintercepts every git-smart-HTTP request globally, so a plugin cannot serve git-over-HTTP under its own prefix.isGitRequest(urlPath)matches by substring regardless of path:The
onRequesthook and the git-handler dispatch inserver.jsboth use it, so a request likeGET /forge/<owner>/<repo>.git/info/refs?service=git-upload-packis grabbed by core's git handler — which looks the repo up under the pod root, doesn't find it, and returns 401 — before the plugin that owns/forgeever sees it.Consumer / proof-of-need
The out-of-tree forge plugin (
JavaScriptSolidServer/plugins) hosts git repos at/forge/<owner>/<name>.gitand serves smart-HTTP itself (viagit-http-backend, thegitscratch/pattern). With--gitenabled on the same server, clone/push to the forge's URLs fail — core shadows them. The forge's browse / UI / anchor / nostr layers work (different paths); only its git transport is shadowed. This blocks using the forge as a real remote on any pod that also runs core--git(which serves the pod's own repos).It's an either/or today:
--giton → pod-git works, forge-git shadowed--gitoff → forge-git works, but the pod's own repos lose git-over-HTTPYou can't have both.
Proposed fix
Core's git dispatch should skip paths owned by a plugin — the loader already knows each plugin's
prefix(and itsapi.reservePathclaims). Options:/forgeowns/forge/*, git or not).api.reservePath-claimed paths from the git guard.gitExclude: ['/forge'].Any of these lets core pod-git and plugin-served git coexist.
Workaround
A one-line local patch to
isGitRequestexcluding the plugin prefix (if (urlPath.startsWith('/forge/')) return false;) restores forge git while keeping pod-git — demo-grade, pending the general fix above.