{"record":{"id":"f6a0d65a6a9e7fbd","repo":"github/github-mcp-server","slug":"viewer-login-returned-empty","errorCode":null,"errorMessage":"viewer login returned empty","messagePattern":"viewer login returned empty","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/lockdown/lockdown.go","lineNumber":160,"sourceCode":"\tc.viewerMu.Lock()\n\tdefer c.viewerMu.Unlock()\n\tif c.viewerLogin != \"\" {\n\t\treturn c.viewerLogin, nil\n\t}\n\tif c.client == nil {\n\t\treturn \"\", fmt.Errorf(\"nil GraphQL client\")\n\t}\n\tvar query struct {\n\t\tViewer struct {\n\t\t\tLogin githubv4.String\n\t\t}\n\t}\n\tif err := c.client.Query(ctx, &query, nil); err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to query viewer login: %w\", err)\n\t}\n\tlogin := strings.ToLower(string(query.Viewer.Login))\n\tif login == \"\" {\n\t\treturn \"\", fmt.Errorf(\"viewer login returned empty\")\n\t}\n\tc.viewerLogin = login\n\treturn c.viewerLogin, nil\n}\n\n// setViewerLogin seeds the cached viewer login from a piggy-backed query response.\nfunc (c *RepoAccessCache) setViewerLogin(login string) {\n\tif login == \"\" {\n\t\treturn\n\t}\n\tc.viewerMu.Lock()\n\tdefer c.viewerMu.Unlock()\n\tif c.viewerLogin == \"\" {\n\t\tc.viewerLogin = strings.ToLower(login)\n\t}\n}\n\nfunc (c *RepoAccessCache) getRepoAccessInfo(ctx context.Context, username, owner, repo string) (RepoAccessInfo, error) {","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/github/github-mcp-server/blob/0ea1f775a7c73eff1bd2e25904d01136756bbfe2/pkg/lockdown/lockdown.go#L142-L178","documentation":"The Viewer GraphQL query succeeded (HTTP 200) but returned an empty login, so the RepoAccessCache refuses to continue - lockdown's comparisons (viewer == content author, per IsSafeContent) are meaningless without a login. This is the signature of tokens with no user context, primarily GitHub App server-to-server installation tokens (ghs_) whose viewer is null.","triggerScenarios":"Lockdown mode enabled while the request authenticates with a ghs_ installation token or any credential whose viewer.login resolves to null/empty; setViewerLogin also refuses to seed an empty value from piggy-backed responses.","commonSituations":"Migrating a deployment from PATs to a GitHub App (server-to-server) without disabling lockdown; machine credentials used where per-user identity is required.","solutions":["Switch the deployment to a user-scoped token: classic PAT (ghp_), fine-grained PAT (github_pat_), or user-to-server app token (ghu_)","If machine tokens are mandatory, disable lockdown mode for that deployment","Confirm the token has a viewer: curl -H \"Authorization: Bearer $TOKEN\" https://api.github.com/user must return a non-empty login"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"func hasUserContext(token string) bool {\n\t// ghs_ = server-to-server installation token: viewer.login is null\n\treturn !strings.HasPrefix(token, \"ghs_\")\n}\nif lockdownEnabled && !hasUserContext(token) {\n\treturn errors.New(\"lockdown requires a user-scoped token; ghs_ tokens have no viewer\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never pair lockdown mode with server-to-server app (ghs_) tokens","Add a startup probe that queries /user once and fails fast on an empty login","Document which token types each deployment mode supports"],"tags":["go","graphql","lockdown","github-app","authentication"],"backgroundTag":null,"analyzedSha":"0ea1f775a7c73eff1bd2e25904d01136756bbfe2","analyzedAt":"2026-08-15T18:10:19.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}