{"record":{"id":"65a391ceabccf595","repo":"github/github-mcp-server","slug":"oauth-callback-listener-could-not-bind","errorCode":null,"errorMessage":"OAuth callback listener could not bind","messagePattern":"OAuth callback listener could not bind","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/oauth/flow.go","lineNumber":19,"sourceCode":"package oauth\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"golang.org/x/oauth2\"\n)\n\n// deviceAuthTimeout bounds the synchronous device-code request made while\n// preparing the device flow (before any waiting on the user).\nconst deviceAuthTimeout = 30 * time.Second\n\n// errCallbackBind marks a failure to bind the local OAuth callback listener, so\n// begin can treat a busy fixed port as fatal without mislabeling unrelated\n// errors (e.g. a failure to generate the state parameter) as a port conflict.\nvar errCallbackBind = errors.New(\"OAuth callback listener could not bind\")\n\n// flowPlan is a prepared authorization flow ready to run in the background.\ntype flowPlan struct {\n\t// run performs the blocking part of the flow (await callback + exchange, or\n\t// poll the device endpoint) and returns the token.\n\trun func(context.Context) (*oauth2.Token, error)\n\t// display, if set, presents the prompt to the user via the Prompter and\n\t// blocks until they act. ErrPromptDeclined (the user said no) or any other\n\t// error aborts the flow, except ErrPromptUnavailable, which degrades to\n\t// fallback when that is set.\n\tdisplay func(context.Context) error\n\t// fallback, if set alongside display, is the manual user action to surface\n\t// when the display prompt cannot be delivered (ErrPromptUnavailable). It lets\n\t// a runtime elicitation failure degrade to the manual channel — keeping the\n\t// background flow alive — instead of aborting.\n\tfallback *UserAction\n\t// userAction, if set, indicates the last-resort channel: the caller must\n\t// surface it and the user retries after authorizing out of band.","sourceCodeStart":1,"sourceCodeEnd":37,"githubUrl":"https://github.com/github/github-mcp-server/blob/0ea1f775a7c73eff1bd2e25904d01136756bbfe2/internal/oauth/flow.go#L1-L37","documentation":"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.","triggerScenarios":"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.","commonSituations":"Hand-assembling cursors by string concatenation; string slicing that clips digits off the end; whitespace introduced when copying values through terminals or config files.","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"],"exampleFix":"// before: negative and malformed offsets\nbase64.RawURLEncoding.EncodeToString([]byte(\"blame-range:-5\"))\n\n// after: non-negative decimal offset\nbase64.RawURLEncoding.EncodeToString([]byte(\"blame-range:100\"))","handlingStrategy":"validation","validationCode":"func validBlameCursorOffset(s string) bool {\n\tif s == \"\" {\n\t\treturn true\n\t}\n\tb, err := base64.RawURLEncoding.DecodeString(s)\n\tif err != nil || !strings.HasPrefix(string(b), \"blame-range:\") {\n\t\treturn false\n\t}\n\tn, err := strconv.Atoi(strings.TrimPrefix(string(b), \"blame-range:\"))\n\treturn err == nil && n >= 0\n}","typeGuard":"func isInvalidCursorError(err error) bool {\n\treturn err != nil && strings.Contains(err.Error(), \"after cursor is invalid\")\n}","tryCatchPattern":"res, err := callGetFileBlame(ctx, args)\nif isInvalidCursorError(err) {\n\t// offset payload is corrupt: drop it and page from the start\n\targs[\"after\"] = \"\"\n\tres, err = callGetFileBlame(ctx, args)\n}","preventionTips":["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"],"tags":["go","pagination","validation","cursor-format"],"backgroundTag":null,"analyzedSha":"0ea1f775a7c73eff1bd2e25904d01136756bbfe2","analyzedAt":"2026-08-15T18:10:19.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}