github/github-mcp-server · error
OAuth callback listener could not bind
Error message
OAuth callback listener could not bind
What it means
Third guard inside decodeBlameCursor: the decoded value has the "blame-range:" prefix but the remainder is not a valid non-negative integer (strconv.Atoi fails or the result is negative). The cursor decoded and matched the format, yet carries a corrupt or hand-mangled offset.
Source
Thrown at internal/oauth/flow.go:19
package oauth
import (
"context"
"errors"
"fmt"
"time"
"golang.org/x/oauth2"
)
// deviceAuthTimeout bounds the synchronous device-code request made while
// preparing the device flow (before any waiting on the user).
const deviceAuthTimeout = 30 * time.Second
// errCallbackBind marks a failure to bind the local OAuth callback listener, so
// begin can treat a busy fixed port as fatal without mislabeling unrelated
// errors (e.g. a failure to generate the state parameter) as a port conflict.
var errCallbackBind = errors.New("OAuth callback listener could not bind")
// flowPlan is a prepared authorization flow ready to run in the background.
type flowPlan struct {
// run performs the blocking part of the flow (await callback + exchange, or
// poll the device endpoint) and returns the token.
run func(context.Context) (*oauth2.Token, error)
// display, if set, presents the prompt to the user via the Prompter and
// blocks until they act. ErrPromptDeclined (the user said no) or any other
// error aborts the flow, except ErrPromptUnavailable, which degrades to
// fallback when that is set.
display func(context.Context) error
// fallback, if set alongside display, is the manual user action to surface
// when the display prompt cannot be delivered (ErrPromptUnavailable). It lets
// a runtime elicitation failure degrade to the manual channel — keeping the
// background flow alive — instead of aborting.
fallback *UserAction
// userAction, if set, indicates the last-resort channel: the caller must
// surface it and the user retries after authorizing out of band.View on GitHub (pinned to 0ea1f775a7)
Solutions
- Prefer the server-issued nextCursor over any locally constructed cursor
- When building one, append exactly strconv.Itoa(n) with n >= 0 and no surrounding whitespace
- Re-request from after:"" if your offset state is suspect
- Validate client-side before sending: decode, strip prefix, Atoi, check n >= 0
Example fix
// before: negative and malformed offsets
base64.RawURLEncoding.EncodeToString([]byte("blame-range:-5"))
// after: non-negative decimal offset
base64.RawURLEncoding.EncodeToString([]byte("blame-range:100")) Defensive patterns
Strategy: validation
Validate before calling
func validBlameCursorOffset(s string) bool {
if s == "" {
return true
}
b, err := base64.RawURLEncoding.DecodeString(s)
if err != nil || !strings.HasPrefix(string(b), "blame-range:") {
return false
}
n, err := strconv.Atoi(strings.TrimPrefix(string(b), "blame-range:"))
return err == nil && n >= 0
} Type guard
func isInvalidCursorError(err error) bool {
return err != nil && strings.Contains(err.Error(), "after cursor is invalid")
} Try / catch
res, err := callGetFileBlame(ctx, args)
if isInvalidCursorError(err) {
// offset payload is corrupt: drop it and page from the start
args["after"] = ""
res, err = callGetFileBlame(ctx, args)
} Prevention
- Build cursors only via the server-issued nextCursor, not string concatenation
- If constructing offsets, use exactly strconv.Itoa(n) with n >= 0
- Never truncate or slice cursor strings
- Validate the full decode-prefix-Atoi chain client-side before sending
When it happens
Trigger: Suffixes like "blame-range:abc", "blame-range:-5" (negative offset), "blame-range:1e3" (Atoi rejects scientific notation), or "blame-range:10 " (trailing whitespace); also partial truncation of a valid cursor.
Common situations: Hand-assembling cursors by string concatenation; string slicing that clips digits off the end; whitespace introduced when copying values through terminals or config files.
Related errors
- installation token response did not contain an expiry
- installation token response did not contain a token
- perPage value %d exceeds maximum of 100
- perPage value %d cannot be negative
- authorization prompt has expired
AI-assisted analysis of github/github-mcp-server@0ea1f775a7 (2026-08-15).
Data as JSON: /api/errors/65a391ceabccf595.
Report an issue: GitHub.