{"record":{"id":"2f353d9a8093a87d","repo":"github/github-mcp-server","slug":"installation-token-response-did-not-contain-an-exp","errorCode":null,"errorMessage":"installation token response did not contain an expiry","messagePattern":"installation token response did not contain an expiry","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/githubapp/githubapp.go","lineNumber":172,"sourceCode":"\t\tsnippet, readErr := io.ReadAll(io.LimitReader(resp.Body, 512))\n\t\tif readErr != nil {\n\t\t\treturn nil, fmt.Errorf(\"installation token request failed: %s (reading response: %w)\", resp.Status, readErr)\n\t\t}\n\t\treturn nil, fmt.Errorf(\"installation token request failed: %s: %s\", resp.Status, strings.TrimSpace(string(snippet)))\n\t}\n\n\tvar body struct {\n\t\tToken     string    `json:\"token\"`\n\t\tExpiresAt time.Time `json:\"expires_at\"`\n\t}\n\tif err := json.NewDecoder(resp.Body).Decode(&body); err != nil {\n\t\treturn nil, fmt.Errorf(\"decoding installation token response: %w\", err)\n\t}\n\tif body.Token == \"\" {\n\t\treturn nil, errors.New(\"installation token response did not contain a token\")\n\t}\n\tif body.ExpiresAt.IsZero() {\n\t\treturn nil, errors.New(\"installation token response did not contain an expiry\")\n\t}\n\treturn &oauth2.Token{\n\t\tAccessToken: body.Token,\n\t\tTokenType:   \"token\",\n\t\tExpiry:      body.ExpiresAt.Add(-refreshBuffer),\n\t}, nil\n}\n\n// Provider caches and refreshes GitHub App installation access tokens.\ntype Provider struct {\n\tsource oauth2.TokenSource\n\tlogger *slog.Logger\n\n\tmu        sync.Mutex\n\terrLogged bool\n}\n\nfunc NewProvider(cfg Config, logger *slog.Logger) (*Provider, error) {","sourceCodeStart":154,"sourceCodeEnd":190,"githubUrl":"https://github.com/github/github-mcp-server/blob/0ea1f775a7c73eff1bd2e25904d01136756bbfe2/internal/githubapp/githubapp.go#L154-L190","documentation":"Second guard inside decodeBlameCursor: the value decoded successfully as base64url but does not start with the blameCursorPrefix constant \"blame-range:\". The server therefore recognizes the bytes as base64 but not as one of its own blame cursors. Cursors from other tools or versions use different internal formats and fail here.","triggerScenarios":"Reusing a GitHub GraphQL page cursor (base64 of \"cursor:...\") from list tools as get_file_blame's after; base64-encoding a bare integer without the prefix; cursors minted by a github-mcp-server version that used a different prefix.","commonSituations":"Generic pagination helpers that share one cursor variable across tools; copy-pasting cursors between sessions/tools; upgrading the server across a cursor format change while keeping stale cursor state.","solutions":["Use only the pageInfo/nextCursor value returned by a prior get_file_blame call on the same server version","Build cursors with the exact format: base64url(\"blame-range:\"+offset)","Reset pagination with after:\"\" when switching tools or after a server upgrade","Keep per-tool cursor state instead of a shared cursor variable"],"exampleFix":"// before: GraphQL cursor from a different tool\n{\"after\":\"Y3Vyc29yOnYyOpHOAA==\"}\n\n// after: blame cursor from the previous get_file_blame page\n{\"after\":\"YmxhbWUtcmFuZ2U6MTAw\"}","handlingStrategy":"validation","validationCode":"func validBlameCursorPrefix(s string) bool {\n\tif s == \"\" {\n\t\treturn true\n\t}\n\tb, err := base64.RawURLEncoding.DecodeString(s)\n\tif err != nil {\n\t\treturn false\n\t}\n\treturn strings.HasPrefix(string(b), \"blame-range:\")\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// wrong cursor source (e.g. a GraphQL cursor from another tool): reset\n\targs[\"after\"] = \"\"\n\tres, err = callGetFileBlame(ctx, args)\n}","preventionTips":["Keep one cursor variable per tool - never share cursors between get_file_blame and list tools","Reset stored cursors after upgrading github-mcp-server","Only feed back values the same tool's pageInfo returned","Empty string is the only valid 'start over' cursor"],"tags":["go","pagination","validation","cursor-format"],"backgroundTag":null,"analyzedSha":"0ea1f775a7c73eff1bd2e25904d01136756bbfe2","analyzedAt":"2026-08-15T18:10:19.804Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}