11package main
22
33import (
4+ "bytes"
45 "context"
56 "encoding/base64"
67 "encoding/json"
78 "flag"
89 "fmt"
910 "io"
11+ "net/http"
1012 "net/url"
1113 "os"
1214 "path/filepath"
@@ -15,7 +17,6 @@ import (
1517
1618 "github.com/pkg/browser"
1719
18- "github.com/sourcegraph/sourcegraph/lib/accesstoken"
1920 "github.com/sourcegraph/sourcegraph/lib/codeintel/upload"
2021 "github.com/sourcegraph/sourcegraph/lib/errors"
2122 "github.com/sourcegraph/sourcegraph/lib/output"
@@ -88,18 +89,19 @@ func handleCodeIntelUpload(args []string) error {
8889 }
8990 }
9091 if err != nil {
91- return handleUploadError (cfg . AccessToken , err )
92+ return handleUploadError (err , "" )
9293 }
9394
9495 client := api .NewClient (api.ClientOpts {
9596 Out : io .Discard ,
9697 Flags : codeintelUploadFlags .apiFlags ,
9798 })
9899
100+ wrappedClient := & errorCapturingClient {inner : client }
99101 uploadOptions := codeintelUploadOptions (out , isSCIPAvailable )
100- uploadID , err := upload .UploadIndex (ctx , codeintelUploadFlags .file , client , uploadOptions )
102+ uploadID , err := upload .UploadIndex (ctx , codeintelUploadFlags .file , wrappedClient , uploadOptions )
101103 if err != nil {
102- return handleUploadError (uploadOptions . SourcegraphInstanceOptions . AccessToken , err )
104+ return handleUploadError (err , wrappedClient . lastErrorBody )
103105 }
104106
105107 uploadURL , err := makeCodeIntelUploadURL (uploadID )
@@ -232,15 +234,62 @@ func (e errorWithHint) Error() string {
232234 return fmt .Sprintf ("%s\n \n %s\n " , e .err , e .hint )
233235}
234236
237+ // errorCapturingClient wraps an upload.Client to capture the response body
238+ // from error responses (401, 403, etc.). The pinned lib version discards the
239+ // body for 401 responses, so we capture it here before the lib sees it.
240+ type errorCapturingClient struct {
241+ inner upload.Client
242+ lastErrorBody string
243+ }
244+
245+ func (c * errorCapturingClient ) Do (req * http.Request ) (* http.Response , error ) {
246+ resp , err := c .inner .Do (req )
247+ if err != nil {
248+ return resp , err
249+ }
250+ if resp .StatusCode == http .StatusUnauthorized || resp .StatusCode == http .StatusForbidden {
251+ body , readErr := io .ReadAll (resp .Body )
252+ resp .Body .Close ()
253+ if readErr == nil && len (bytes .TrimSpace (body )) > 0 && ! bytes .HasPrefix (bytes .TrimSpace (body ), []byte {'<' }) {
254+ c .lastErrorBody = string (bytes .TrimSpace (body ))
255+ }
256+ // Replace body so the lib can still read it
257+ resp .Body = io .NopCloser (bytes .NewReader (body ))
258+ }
259+ return resp , nil
260+ }
261+
235262// handleUploadError writes the given error to the given output. If the
236263// given output object is nil then the error will be written to standard out.
237264//
238265// This method returns the error that should be passed back up to the runner.
239- func handleUploadError (accessToken string , err error ) error {
266+ func handleUploadError (err error , serverMessage string ) error {
240267 if errors .Is (err , upload .ErrUnauthorized ) {
241- err = attachHintsForAuthorizationError (accessToken , err )
268+ if serverMessage != "" {
269+ // The server provided a specific error message (e.g. "must provide gitlab_token").
270+ // Show it directly so the user knows what went wrong.
271+ err = errorWithHint {
272+ err : errors .Newf ("upload rejected by server: %s" , serverMessage ),
273+ hint : codeHostTokenHint (),
274+ }
275+ } else {
276+ // Generic 401 with no server message — likely a missing or invalid Sourcegraph access token.
277+ err = errorWithHint {
278+ err : errors .New ("upload failed: unauthorized (check your Sourcegraph access token)" ),
279+ hint : "To create a Sourcegraph access token, see https://sourcegraph.com/docs/cli/how-tos/creating_an_access_token." ,
280+ }
281+ }
282+ } else if strings .Contains (err .Error (), "unexpected status code: 403" ) {
283+ // TODO: replace with errors.Is(err, upload.ErrForbidden) once lib snapshot is updated.
284+ displayMsg := "upload failed: forbidden"
285+ if serverMessage != "" {
286+ displayMsg = fmt .Sprintf ("upload rejected by server: %s" , serverMessage )
287+ }
288+ err = errorWithHint {
289+ err : errors .New (displayMsg ),
290+ hint : codeHostTokenHint (),
291+ }
242292 }
243-
244293 if codeintelUploadFlags .ignoreUploadFailures {
245294 // Report but don't return the error
246295 fmt .Println (err .Error ())
@@ -250,76 +299,23 @@ func handleUploadError(accessToken string, err error) error {
250299 return err
251300}
252301
253- func attachHintsForAuthorizationError (accessToken string , originalError error ) error {
254- var actionableHints []string
255-
256- likelyTokenError := accessToken == ""
257- if _ , parseErr := accesstoken .ParsePersonalAccessToken (accessToken ); accessToken != "" && parseErr != nil {
258- likelyTokenError = true
259- actionableHints = append (actionableHints ,
260- "However, the provided access token does not match expected format; was it truncated?" ,
261- "Typically the access token looks like sgp_<40 hex chars> or sgp_<instance-id>_<40 hex chars>." )
302+ // codeHostTokenHint returns documentation links relevant to the upload failure.
303+ // Always includes the general upload docs, and adds code-host-specific token
304+ // documentation if the user supplied a code host token flag.
305+ func codeHostTokenHint () string {
306+ hint := "For more details on uploading SCIP indexes, see https://sourcegraph.com/docs/cli/references/code-intel/upload."
307+ if codeintelUploadFlags .gitHubToken != "" {
308+ hint += "\n If the issue is related to your GitHub token, see https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens."
262309 }
263-
264- if likelyTokenError {
265- return errorWithHint {err : originalError , hint : strings .Join (mergeStringSlices (
266- []string {"A Sourcegraph access token must be provided via SRC_ACCESS_TOKEN for uploading SCIP/LSIF data." },
267- actionableHints ,
268- []string {"For more details, see https://sourcegraph.com/docs/cli/how-tos/creating_an_access_token." },
269- ), "\n " )}
310+ if codeintelUploadFlags .gitLabToken != "" {
311+ hint += "\n If the issue is related to your GitLab token, see https://docs.gitlab.com/user/profile/personal_access_tokens/."
270312 }
271-
272- needsGitHubToken := strings .HasPrefix (codeintelUploadFlags .repo , "github.com" )
273- needsGitLabToken := strings .HasPrefix (codeintelUploadFlags .repo , "gitlab.com" )
274-
275- if needsGitHubToken {
276- if codeintelUploadFlags .gitHubToken != "" {
277- actionableHints = append (actionableHints ,
278- fmt .Sprintf ("The supplied -github-token does not indicate that you have collaborator access to %s." , codeintelUploadFlags .repo ),
279- "Please check the value of the supplied token and its permissions on the code host and try again." ,
280- )
281- } else {
282- actionableHints = append (actionableHints ,
283- fmt .Sprintf ("Please retry your request with a -github-token=XXX with collaborator access to %s." , codeintelUploadFlags .repo ),
284- "This token will be used to check with the code host that the uploading user has write access to the target repository." ,
285- )
286- }
287- } else if needsGitLabToken {
288- if codeintelUploadFlags .gitLabToken != "" {
289- actionableHints = append (actionableHints ,
290- fmt .Sprintf ("The supplied -gitlab-token does not indicate that you have write access to %s." , codeintelUploadFlags .repo ),
291- "Please check the value of the supplied token and its permissions on the code host and try again." ,
292- )
293- } else {
294- actionableHints = append (actionableHints ,
295- fmt .Sprintf ("Please retry your request with a -gitlab-token=XXX with write access to %s." , codeintelUploadFlags .repo ),
296- "This token will be used to check with the code host that the uploading user has write access to the target repository." ,
297- )
298- }
299- } else {
300- actionableHints = append (actionableHints ,
301- "Verification is supported for the following code hosts: github.com, gitlab.com." ,
302- "Please request support for additional code host verification at https://github.com/sourcegraph/sourcegraph/issues/4967." ,
303- )
304- }
305-
306- return errorWithHint {err : originalError , hint : strings .Join (mergeStringSlices (
307- []string {"This Sourcegraph instance has enforced auth for SCIP/LSIF uploads." },
308- actionableHints ,
309- []string {"For more details, see https://docs.sourcegraph.com/cli/references/code-intel/upload." },
310- ), "\n " )}
313+ return hint
311314}
312315
313316// emergencyOutput creates a default Output object writing to standard out.
314317func emergencyOutput () * output.Output {
315318 return output .NewOutput (os .Stdout , output.OutputOpts {})
316319}
317320
318- func mergeStringSlices (ss ... []string ) []string {
319- var combined []string
320- for _ , s := range ss {
321- combined = append (combined , s ... )
322- }
323321
324- return combined
325- }
0 commit comments