Troubleshooting
“Not found” on a repository I can see in the browser
Section titled ““Not found” on a repository I can see in the browser”You are unauthenticated for that host. A private repository returns 404, not 403 — that is the forge’s standard answer for “you cannot see this”, and it looks identical to a typo in the repository name.
Walk the resolution chain for your host. Tokens resolve per host, first hit wins:
- A SaaS-scoped environment variable
- The host’s
tokenin config ($VARexpanded) - The host’s
token_cmd, run throughsh -cand cached for the process gh auth token --hostname <host>— GitHub hosts only, if[forge] cli_token_fallbackis on
| Host | Environment variable |
|---|---|
github.com |
GITHUB_TOKEN, then GH_TOKEN |
| Other GitHub hosts | GH_ENTERPRISE_TOKEN |
gitlab.com |
GITLAB_TOKEN |
dev.azure.com |
AZURE_DEVOPS_EXT_PAT |
codeberg.org |
FORGEJO_TOKEN, then CODEBERG_TOKEN |
Variables are host-scoped on purpose: GITHUB_TOKEN is not read for a GitHub
Enterprise host, so a personal token never leaks to your employer’s instance. If
you set the wrong one for your host, mrman behaves as though you set nothing.
Proving which link fired
Section titled “Proving which link fired”Hide gh and unset the variable. If a private repository starts 404ing, step 4
was doing the work:
$ env PATH=/tmp MRMAN_LIVE_PR=OWNER/REPO#1 \ go test -count=1 ./internal/forge/githubf/ -run TestLivePullRequestListPullRequests: github: list_pull_requests: not found (HTTP 404) on github.comSupply the token directly and it works with gh still off PATH — which is also
the proof that mrman uses the API SDKs rather than the CLI:
$ env PATH=/tmp GITHUB_TOKEN="$(gh auth token)" MRMAN_LIVE_PR=OWNER/REPO#1 \ go test -count=1 ./internal/forge/githubf/ -run TestLivePullRequest--- PASS-count=1 matters. Go caches test results, so without it a “pass” may be a
cached run from before you changed anything.
token_cmd failing
Section titled “token_cmd failing”A token_cmd that exits non-zero is fatal, not skipped — you configured it
on purpose, so falling through to anonymous access would hide the problem. One
that exits 0 and prints nothing counts as “no token” and does fall through,
to gh auth token and then anonymous access. Run the command yourself; it goes
through sh -c (cmd /C on Windows), so quoting is on you, and check that it
actually prints the token.
A host entry may set token or token_cmd, never both. The schema rejects
that rather than picking one silently.
Azure DevOps refuses a post: TF401027 … permission
Section titled “Azure DevOps refuses a post: TF401027 … permission”TF401027: You need the Git 'PullRequestContribute' permission … identity '…'
means the token was accepted, and its scope is fine. The identity behind
it lacks a permission on the repository, and a broader PAT scope will not
fix that. Grant the named permission under Project settings → Repositories →
the repository → Security. mrman’s hint names the permission as that page
does: PullRequestContribute is Contribute to pull requests.
In a pipeline that runs mrman with System.AccessToken (for example
vizzini-bot), the identity is Build\…, the pipeline’s build service. Grant the
permission to
GitHub refuses to resolve a thread: Resource not accessible by integration
Section titled “GitHub refuses to resolve a thread: Resource not accessible by integration”mrman review resolve (or vizzini-bot resolving its fixed threads) fails on
GitHub with resolve_thread: permission denied … Resource not accessible by integration.
GitHub lets a token resolve review threads only with Contents write access. Pull requests write is enough to comment and reply, but not to resolve. The error’s hint says so.
- GitHub Actions (
GITHUB_TOKEN): addcontents: writeto the job’spermissions. It also lets the token push to the repository, so grant it only where you want the bot to resolve its threads. - Fine-grained token: give it Contents: Read & write on the repository.
Replies are unaffected: review reply needs only pull requests write.
“cannot determine the forge for host”
Section titled ““cannot determine the forge for host””cannot determine the forge for host "git.mycorp.com" — add it to your config:
[[forge.hosts]]host = "git.mycorp.com"forge = "github" # one of: github, gitlab, azuredevops, forgejomrman claims github.com, gitlab.com, dev.azure.com and codeberg.org
automatically. Anything else needs an entry. Paste the snippet from the error and
set forge correctly.
If the host is a GitHub Enterprise Server, remember api_base:
api_base = "https://ghe.mycorp.com/api/v3"GitLab and Forgejo usually need no api_base — mrman derives /api/v4 and
/api/v1 from the host.
A bare MR number does not work
Section titled “A bare MR number does not work”bare MR number "125" requires a repository checkout; run inside a checkout,or pass owner/repo#125 or a full URLYou are outside a checkout, or mrman could not resolve a forge repository from
your remotes. Pass a coordinate or a URL. From inside a checkout with an
unrecognized remote host, add the [[forge.hosts]] entry above.
TLS errors on an on-premise host
Section titled “TLS errors on an on-premise host”Add your organization’s CA for that host:
ca_file = "/etc/ssl/corp-root.pem"insecure_skip_verify = true also exists per host. It disables certificate
verification for that host entirely and should stay a last resort.
My config option is being ignored
Section titled “My config option is being ignored”Configuration is validated against embedded CUE schemas, and a mistake never stops mrman starting: the offending key is dropped, everything else is kept, and you get a precise warning in the status bar. If an option seems inert, look at the status bar on startup — the warning names the key.
Two specific gotchas:
backendis accepted and deliberately ignored. It exists for tuicr config compatibility; mrman uses the git CLI by design.ignore_whitespaceapplies to local git and Jujutsu diffs only. A pull request’s diff arrives from the forge already rendered, so there is no flag left to re-run it with.
Keys do not do what the docs say
Section titled “Keys do not do what the docs say”Almost always the terminal. Shift-Enter needs the kitty keyboard protocol;
Alt-Enter survives tmux; Ctrl-j works without either. See Terminal
Setup for the full set of aliases and which terminals
support what.
If the mouse has taken over your terminal’s own text selection, hold Shift (or
Option on macOS), or set mouse = false.
mrman hangs at startup under script or a pipe
Section titled “mrman hangs at startup under script or a pipe”script -qec "mrman pr 1" /dev/null # hangsBubble Tea queries the terminal for capabilities at startup and a piped stdin
never answers. This is not fixable from mrman’s side. For non-interactive use,
that is what --json and the mrman review commands are
for.
--auto is refused
Section titled “--auto is refused”error: --auto requires an interactive terminalerror: if any flags in the group [json auto] are set none of the others can beBoth are intentional. --auto authorizes an agent to submit reviews, so it must
come from a person at a terminal — see the submit
interlock.
Submit posted some comments and not others
Section titled “Submit posted some comments and not others”On GitLab and Azure DevOps there is no atomic-submit endpoint, so mrman posts a sequence and reports a partial submit if a step fails. Only the comments that landed are marked submitted; the rest stay editable, so you can retry without double-posting. Details: Forge Capabilities.
A comment went into “Unplaced comments”
Section titled “A comment went into “Unplaced comments””mrman could not anchor it to a line the forge would accept, so it moved into the review body rather than being dropped. The reason is stated:
| Reason | Fix |
|---|---|
line not in current diff |
The line is outside every hunk — comment on a changed line, or leave it review-level |
anchored line changed since the comment was written |
The code the comment describes moved out of reach or is no longer identifiable — see outdated comments |
range spans both diff sides |
Keep an inline range on one side |
no valid anchor line |
A file-level comment found no new-side line |
binary file |
Nothing to anchor to |
file too large |
The file was not diffed |
If you are writing comments through mrman review add, note that setting a
cursor position by hand is not the same as navigating: comments file under the
current file, so an out-of-band cursor move can aim a comment at the wrong
file. mrman catches this and reports “line not in current diff” rather than
posting somewhere wrong.
A comment is marked (outdated)
Section titled “A comment is marked (outdated)”The diff moved under it. Every comment records the line it was written about, and
on reopen or :e mrman checks that record against the current diff. (outdated)
means the check failed one of two ways:
- The line is gone. The code it described is no longer in the diff.
- The line is ambiguous. Its content now appears in several places, so there
is no telling which one was meant. A comment on a lone
}is the usual case.
mrman deliberately does not guess. The old line number still resolves to some line, and posting there would attach your criticism to code you never read — so the comment keeps its text, wears the badge, and at submit time goes to the resolver instead of inline. Edit it, re-place it by hand, or send it to the review body.
(moved) is the benign sibling: the content was found at exactly one other
line and the comment followed it. Nothing is wrong; the badge exists so a
comment that silently changed position tells you it did.
My review did not come back after a rebase or amend
Section titled “My review did not come back after a rebase or amend”A working-tree review is carried onto the new HEAD automatically, so this usually means one of the deliberate exclusions applied:
| Situation | Why |
|---|---|
| Detached HEAD | The identity is the commit, so there is no stable anchor to carry — two detached checkouts are unrelated positions, not one review |
| You switched branches | A review is anchored to its branch |
A commit range (-r) |
A range names its own endpoints, so a different range is a different review |
| Nothing was in it | An untouched session is not carried, to avoid churning files on every commit |
| Staged vs working tree | Different diff sources are different reviews |
The previous session is not deleted in any of these cases. Find it with
mrman review list --repo . — the anchor column shows which HEAD each one
belongs to.
--patch refuses a pipe or process substitution
Section titled “--patch refuses a pipe or process substitution”invalid input: /dev/fd/63 is not a regular filemrman re-reads the artifact every time you reload with :e, and a pipe can
only be read once. Redirect to a file first:
git format-patch --stdout -3 > /tmp/series.mboxmrman --patch /tmp/series.mboxSelecting a patch says it carries no diff
Section titled “Selecting a patch says it carries no diff”Load failed: that message is prose only — a cover letter carries no diffYou selected the 0/N cover letter on its own. It is a row in the commit strip
like any other, but it holds the series’ prose rather than a change. Read it
there, or select it together with a patch that does carry a diff.
go install fails
Section titled “go install fails”go install github.com/infrashift/mrman@latest needs Go 1.26.11 or newer
(the go 1.26.11 line in go.mod makes an older Go fetch it automatically when GOTOOLCHAIN
allows). It resolves the tagged charmkit module like any other dependency,
and CI checks that shape (GOWORK=off go build ./...) on every change. If it
still fails, building from a checkout (make install) narrows it down. See
Contributing.
Something else
Section titled “Something else”Please open an issue with your
terminal, forge, and what you expected. If it is a forge-behavior question,
mrman --version and the exact error text are the two most useful things to
include.