{"record":{"id":"438fd67e720c6f3b","repo":"gastownhall/beads","slug":"oauth-token-response-missing-access-token","errorCode":null,"errorMessage":"oauth: token response missing access_token","messagePattern":"oauth: token response missing access_token","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/linear/oauth.go","lineNumber":154,"sourceCode":"\tif err != nil {\n\t\treturn fmt.Errorf(\"oauth: failed to read token response: %w\", err)\n\t}\n\n\tif resp.StatusCode != http.StatusOK {\n\t\tvar errResp oauthErrorResponse\n\t\tif json.Unmarshal(body, &errResp) == nil && errResp.Error != \"\" {\n\t\t\treturn fmt.Errorf(\"oauth: token request failed (%s): %s\", errResp.Error, errResp.Description)\n\t\t}\n\t\treturn fmt.Errorf(\"oauth: token request returned status %d: %s\", resp.StatusCode, string(body))\n\t}\n\n\tvar tokenResp oauthTokenResponse\n\tif err := json.Unmarshal(body, &tokenResp); err != nil {\n\t\treturn fmt.Errorf(\"oauth: failed to parse token response: %w\", err)\n\t}\n\n\tif tokenResp.AccessToken == \"\" {\n\t\treturn fmt.Errorf(\"oauth: token response missing access_token\")\n\t}\n\n\tm.token = tokenResp.AccessToken\n\tm.expiresAt = m.nowFunc().Add(time.Duration(tokenResp.ExpiresIn) * time.Second)\n\n\tdebug.Logf(\"oauth: acquired token (expires in %ds)\", tokenResp.ExpiresIn)\n\treturn nil\n}\n","sourceCodeStart":136,"sourceCodeEnd":163,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/linear/oauth.go#L136-L163","documentation":"The token response parsed successfully as JSON but contained an empty access_token field. The server answered 200 with a syntactically valid but semantically empty token payload, so no usable credential was obtained.","triggerScenarios":"tokenResp.AccessToken == \"\" after successful json.Unmarshal in acquireToken — e.g. the server returned {} or only error/description fields with status 200.","commonSituations":"Provider quirk where errors are returned with 200; grant_type not actually supported so the payload lacks a token; a mock/stub server or misconfigured test double in the path.","solutions":["Verify grant_type and credentials produce a real grant at the provider (test with curl).","Check for provider responses that embed errors with 200 and handle the error fields explicitly.","Point the client at the real token endpoint instead of a stub/mock in the deployed environment.","Update the client if the provider changed the token response schema."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Pre-flight: request a token and check the field yourself\nvar probe struct{ AccessToken string `json:\"access_token\"` }\n_ = json.Unmarshal(body, &probe)\nif probe.AccessToken == \"\" { /* fix grant/credentials before running */ }","typeGuard":"func hasAccessToken(r map[string]any) bool {\n    t, ok := r[\"access_token\"].(string)\n    return ok && t != \"\"\n}","tryCatchPattern":null,"preventionTips":["Test the grant flow with curl before wiring it into the app.","Ensure grant_type matches what the provider actually supports.","Remove stub/mock token servers from production paths."],"tags":["oauth","access-token","response-validation","go"],"backgroundTag":"missing-access-token","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}