Summary
worktree_base_path requires the clone's remote literally named origin to be the tracked repo. That is the inverse of the layout gh repo fork produces, so a contributor with a stock fork clone cannot point middleman at it without re-plumbing their remotes first.
Repro
A stock fork clone (gh repo fork kenn-io/agentsview --clone):
origin https://github.com/<you>/agentsview.git
upstream https://github.com/kenn-io/agentsview.git
[[repos]]
owner = "kenn-io"
name = "agentsview"
worktree_base_path = "/path/to/agentsview"
$ middleman api -i -d '{"worktree_base_path":"/path/to/agentsview"}' \
PUT /api/v1/repo/github/kenn-io/agentsview/worktree-base
HTTP/1.1 400 Bad Request
{"title":"Bad Request","status":400,"detail":"origin remote does not match repository:
clone remote repo \"<you>/agentsview\" does not match configured repo \"kenn-io/agentsview\"",
"code":"validationError","details":{"field":"body.worktree_base_path"}}
A hand-edited config.toml gets the same rejection later instead, at workspace-creation time (workspaceSetupGitDir -> ValidateWorktreeBasePath).
Why this is worth changing
From gh repo fork --help:
By default, the new fork is set to be your origin remote and any existing origin remote is renamed to upstream.
So the default GitHub fork workflow produces exactly the layout we reject. The workaround is to invert the remotes:
git remote rename origin fork
git remote rename upstream origin
git config remote.pushDefault fork # keep bare `git push` going to the fork
git config push.default current # `simple` breaks @{push} once triangular
That works fine (and git rewrites pushDefault, branch.*.remote, fetch refspecs and gh-resolved for you), but it asks users to adopt a non-default convention to satisfy a config field, and the error message never hints that the name origin is what's load-bearing.
Root cause
The requirement is functional, not merely defensive: we fetch by the literal remote name.
internal/workspace/manager.go:3177 fetchWorkspaceBaseWithGit -> fetch --prune --no-tags --negotiation-tip=refs/remotes/origin/* origin +refs/heads/*:refs/remotes/origin/*
internal/workspace/manager.go:3162 fetchWorkspaceMergeRequestHeadRefWithGit -> fetch ... origin +<ref>:<ref>
If origin were the fork, that would populate the tracked repo's branches with fork data — so the validation is right to refuse:
internal/workspace/manager.go:1091 ValidateWorktreeBasePath
internal/workspace/manager.go:1204 / :1224 validateOriginRemoteURLs / validateOriginRemoteURL -> gitremote.ValidateRemoteIdentity
internal/workspace/manager.go:1293 validateOriginFetchRefspec (asserts remote.origin.fetch updates refs/remotes/origin/*)
The real constraint is "we fetch by name", which is an implementation choice rather than an inherent one.
Proposed fix
Identify the remote by URL identity rather than by name:
- Resolve the base remote as the one whose URL matches the tracked repo identity.
gitremote.ValidateRemoteIdentity is already exactly that predicate — iterate git remote instead of assuming origin. Optionally accept an explicit remote = "..." on the repo config entry to override, defaulting to auto-detect.
- Thread the resolved name through the places that hardcode
origin:
validateOriginFetchRefspec -> assert remote.<name>.fetch maps to refs/remotes/<name>/*
- both fetch helpers -> fetch
<name>, --negotiation-tip=refs/remotes/<name>/*
- the origin-HEAD refresh path (
refreshWorkspaceBaseOriginHeadWithGit)
- Keep rejecting the genuinely ambiguous cases: no remote matches the identity, or two or more do.
If that's more surface than we want, a cheap alternative: keep the origin-only rule but make the 400 actionable — name the offending remote and suggest the rename, so users aren't left guessing that origin specifically is the requirement.
Related (possibly split out)
Even once configured, worktree_base_path is skipped for cross-fork PR workspaces: internal/workspace/manager.go:1001 workspaceSetupGitDir only consults it when ws.MRHeadRepo == nil, and internal/db/types.go:735 documents that field as "nil for same-repo PRs".
So under a fork-based PR flow the field only benefits issue workspaces and same-repo PRs. That compounds the above — the users most likely to have a fork clone get the least out of the feature. Happy to split this into its own issue if it's a separate concern.
Summary
worktree_base_pathrequires the clone's remote literally namedoriginto be the tracked repo. That is the inverse of the layoutgh repo forkproduces, so a contributor with a stock fork clone cannot point middleman at it without re-plumbing their remotes first.Repro
A stock fork clone (
gh repo fork kenn-io/agentsview --clone):A hand-edited
config.tomlgets the same rejection later instead, at workspace-creation time (workspaceSetupGitDir->ValidateWorktreeBasePath).Why this is worth changing
From
gh repo fork --help:So the default GitHub fork workflow produces exactly the layout we reject. The workaround is to invert the remotes:
That works fine (and git rewrites
pushDefault,branch.*.remote, fetch refspecs andgh-resolvedfor you), but it asks users to adopt a non-default convention to satisfy a config field, and the error message never hints that the nameoriginis what's load-bearing.Root cause
The requirement is functional, not merely defensive: we fetch by the literal remote name.
internal/workspace/manager.go:3177fetchWorkspaceBaseWithGit->fetch --prune --no-tags --negotiation-tip=refs/remotes/origin/* origin +refs/heads/*:refs/remotes/origin/*internal/workspace/manager.go:3162fetchWorkspaceMergeRequestHeadRefWithGit->fetch ... origin +<ref>:<ref>If
originwere the fork, that would populate the tracked repo's branches with fork data — so the validation is right to refuse:internal/workspace/manager.go:1091ValidateWorktreeBasePathinternal/workspace/manager.go:1204/:1224validateOriginRemoteURLs/validateOriginRemoteURL->gitremote.ValidateRemoteIdentityinternal/workspace/manager.go:1293validateOriginFetchRefspec(assertsremote.origin.fetchupdatesrefs/remotes/origin/*)The real constraint is "we fetch by name", which is an implementation choice rather than an inherent one.
Proposed fix
Identify the remote by URL identity rather than by name:
gitremote.ValidateRemoteIdentityis already exactly that predicate — iterategit remoteinstead of assumingorigin. Optionally accept an explicitremote = "..."on the repo config entry to override, defaulting to auto-detect.origin:validateOriginFetchRefspec-> assertremote.<name>.fetchmaps torefs/remotes/<name>/*<name>,--negotiation-tip=refs/remotes/<name>/*refreshWorkspaceBaseOriginHeadWithGit)If that's more surface than we want, a cheap alternative: keep the origin-only rule but make the 400 actionable — name the offending remote and suggest the rename, so users aren't left guessing that
originspecifically is the requirement.Related (possibly split out)
Even once configured,
worktree_base_pathis skipped for cross-fork PR workspaces:internal/workspace/manager.go:1001workspaceSetupGitDironly consults it whenws.MRHeadRepo == nil, andinternal/db/types.go:735documents that field as "nil for same-repo PRs".So under a fork-based PR flow the field only benefits issue workspaces and same-repo PRs. That compounds the above — the users most likely to have a fork clone get the least out of the feature. Happy to split this into its own issue if it's a separate concern.