@@ -71,7 +71,7 @@ func handleCodeIntelUpload(args []string) error {
7171 }
7272 }
7373 if err != nil {
74- return handleUploadError ( cfg . AccessToken , err )
74+ return err
7575 }
7676
7777 client := cfg .apiClient (codeintelUploadFlags .apiFlags , io .Discard )
@@ -212,94 +212,116 @@ func (e errorWithHint) Error() string {
212212 return fmt .Sprintf ("%s\n \n %s\n " , e .err , e .hint )
213213}
214214
215- // handleUploadError writes the given error to the given output. If the
216- // given output object is nil then the error will be written to standard out.
217- //
218- // This method returns the error that should be passed back up to the runner.
215+ // handleUploadError attaches actionable hints to upload errors and returns
216+ // the enriched error. Only called for actual upload failures (not flag validation).
219217func handleUploadError (accessToken string , err error ) error {
220- if errors .Is (err , upload .ErrUnauthorized ) {
221- err = attachHintsForAuthorizationError (accessToken , err )
218+ var httpErr * ErrUnexpectedStatusCode
219+ isUnauthorized := errors .As (err , & httpErr ) && httpErr .Code == 401
220+ isForbidden := errors .As (err , & httpErr ) && httpErr .Code == 403
221+
222+ if isUnauthorized || isForbidden {
223+ displayErr := errors .Newf ("upload failed: %s" , uploadFailureReason (httpErr ))
224+
225+ err = errorWithHint {
226+ err : displayErr ,
227+ hint : uploadHints (accessToken , isUnauthorized , isForbidden ),
228+ }
222229 }
223230
224231 if codeintelUploadFlags .ignoreUploadFailures {
225- // Report but don't return the error
226232 fmt .Println (err .Error ())
227233 return nil
228234 }
229235
230236 return err
231237}
232238
233- func attachHintsForAuthorizationError (accessToken string , originalError error ) error {
234- var actionableHints []string
239+ // uploadHints builds hint paragraphs for the Sourcegraph access token,
240+ // code host tokens, and a docs link.
241+ func uploadHints (accessToken string , isUnauthorized , isForbidden bool ) string {
242+ var hints []string
235243
236- likelyTokenError := accessToken == ""
237- if _ , parseErr := accesstoken .ParsePersonalAccessToken (accessToken ); accessToken != "" && parseErr != nil {
238- likelyTokenError = true
239- actionableHints = append (actionableHints ,
240- "However, the provided access token does not match expected format; was it truncated?" ,
241- "Typically the access token looks like sgp_<40 hex chars> or sgp_<instance-id>_<40 hex chars>." )
244+ if h := sourcegraphAccessTokenHint (accessToken , isUnauthorized , isForbidden ); h != "" {
245+ hints = append (hints , h )
242246 }
243247
244- if likelyTokenError {
245- return errorWithHint {err : originalError , hint : strings .Join (mergeStringSlices (
246- []string {"A Sourcegraph access token must be provided via SRC_ACCESS_TOKEN for uploading SCIP data." },
247- actionableHints ,
248- []string {"For more details, see https://sourcegraph.com/docs/cli/how-tos/creating_an_access_token." },
249- ), "\n " )}
250- }
248+ hints = append (hints , codeHostTokenHints (isUnauthorized )... )
251249
252- needsGitHubToken := strings .HasPrefix (codeintelUploadFlags .repo , "github.com" )
253- needsGitLabToken := strings .HasPrefix (codeintelUploadFlags .repo , "gitlab.com" )
250+ hints = append (hints , "For more details on uploading SCIP indexes, see https://sourcegraph.com/docs/cli/references/code-intel/upload." )
254251
255- if needsGitHubToken {
256- if codeintelUploadFlags .gitHubToken != "" {
257- actionableHints = append (actionableHints ,
258- fmt .Sprintf ("The supplied -github-token does not indicate that you have collaborator access to %s." , codeintelUploadFlags .repo ),
259- "Please check the value of the supplied token and its permissions on the code host and try again." ,
260- )
261- } else {
262- actionableHints = append (actionableHints ,
263- fmt .Sprintf ("Please retry your request with a -github-token=XXX with collaborator access to %s." , codeintelUploadFlags .repo ),
264- "This token will be used to check with the code host that the uploading user has write access to the target repository." ,
265- )
252+ return strings .Join (hints , "\n \n " )
253+ }
254+
255+ // sourcegraphAccessTokenHint returns a hint about the Sourcegraph access token
256+ // based on the error type and token state.
257+ func sourcegraphAccessTokenHint (accessToken string , isUnauthorized , isForbidden bool ) string {
258+ if isUnauthorized {
259+ if accessToken == "" {
260+ return "No Sourcegraph access token was provided. Set the SRC_ACCESS_TOKEN environment variable to a valid token."
266261 }
267- } else if needsGitLabToken {
268- if codeintelUploadFlags .gitLabToken != "" {
269- actionableHints = append (actionableHints ,
270- fmt .Sprintf ("The supplied -gitlab-token does not indicate that you have write access to %s." , codeintelUploadFlags .repo ),
271- "Please check the value of the supplied token and its permissions on the code host and try again." ,
272- )
273- } else {
274- actionableHints = append (actionableHints ,
275- fmt .Sprintf ("Please retry your request with a -gitlab-token=XXX with write access to %s." , codeintelUploadFlags .repo ),
276- "This token will be used to check with the code host that the uploading user has write access to the target repository." ,
277- )
262+ if _ , parseErr := accesstoken .ParsePersonalAccessToken (accessToken ); parseErr != nil {
263+ return "The provided Sourcegraph access token does not match the expected format (sgp_<40 hex chars> or sgp_<instance-id>_<40 hex chars>). Was it copied incorrectly or truncated?"
278264 }
279- } else {
280- actionableHints = append (actionableHints ,
281- "Verification is supported for the following code hosts: github.com, gitlab.com." ,
282- "Please request support for additional code host verification at https://github.com/sourcegraph/sourcegraph/issues/4967." ,
283- )
265+ return "The Sourcegraph access token may be invalid, expired, or you may be connecting to the wrong Sourcegraph instance."
284266 }
267+ if isForbidden {
268+ return "You may not have sufficient permissions on this Sourcegraph instance."
269+ }
270+ return ""
271+ }
285272
286- return errorWithHint {err : originalError , hint : strings .Join (mergeStringSlices (
287- []string {"This Sourcegraph instance has enforced auth for SCIP uploads." },
288- actionableHints ,
289- []string {"For more details, see https://docs.sourcegraph.com/cli/references/code-intel/upload." },
290- ), "\n " )}
273+ // codeHostTokenHints returns hints about GitHub or GitLab tokens.
274+ func codeHostTokenHints (isUnauthorized bool ) []string {
275+ if codeintelUploadFlags .gitHubToken != "" || strings .HasPrefix (codeintelUploadFlags .repo , "github.com" ) {
276+ return []string {gitHubTokenHint (isUnauthorized )}
277+ }
278+ if codeintelUploadFlags .gitLabToken != "" || strings .HasPrefix (codeintelUploadFlags .repo , "gitlab.com" ) {
279+ return []string {gitLabTokenHint (isUnauthorized )}
280+ }
281+ return []string {"Code host verification is supported for github.com and gitlab.com repositories." }
291282}
292283
293- // emergencyOutput creates a default Output object writing to standard out.
294- func emergencyOutput () * output.Output {
295- return output .NewOutput (os .Stdout , output.OutputOpts {})
284+ // gitHubTokenHint returns a hint about the GitHub token, or empty if not applicable.
285+ func gitHubTokenHint (isUnauthorized bool ) string {
286+ if codeintelUploadFlags .gitHubToken != "" {
287+ if isUnauthorized {
288+ return "The supplied -github-token may be invalid."
289+ }
290+ return "The supplied -github-token may lack the required permissions."
291+ }
292+ if strings .HasPrefix (codeintelUploadFlags .repo , "github.com" ) {
293+ return fmt .Sprintf ("No -github-token was provided. If this Sourcegraph instance enforces code host authentication, retry with -github-token=<token> for a token with access to %s." , codeintelUploadFlags .repo )
294+ }
295+ return ""
296296}
297297
298- func mergeStringSlices (ss ... []string ) []string {
299- var combined []string
300- for _ , s := range ss {
301- combined = append (combined , s ... )
298+ // gitLabTokenHint returns a hint about the GitLab token, or empty if not applicable.
299+ func gitLabTokenHint (isUnauthorized bool ) string {
300+ if codeintelUploadFlags .gitLabToken != "" {
301+ if isUnauthorized {
302+ return "The supplied -gitlab-token may be invalid."
303+ }
304+ return "The supplied -gitlab-token may lack the required permissions."
302305 }
306+ if strings .HasPrefix (codeintelUploadFlags .repo , "gitlab.com" ) {
307+ return fmt .Sprintf ("No -gitlab-token was provided. If this Sourcegraph instance enforces code host authentication, retry with -gitlab-token=<token> for a token with access to %s." , codeintelUploadFlags .repo )
308+ }
309+ return ""
310+ }
303311
304- return combined
312+ // uploadFailureReason returns the server's response body if available, or a
313+ // generic reason derived from the HTTP status code.
314+ func uploadFailureReason (httpErr * ErrUnexpectedStatusCode ) string {
315+ if httpErr .Body != "" {
316+ return httpErr .Body
317+ }
318+ if httpErr .Code == 401 {
319+ return "unauthorized"
320+ }
321+ return "forbidden"
322+ }
323+
324+ // emergencyOutput creates a default Output object writing to standard out.
325+ func emergencyOutput () * output.Output {
326+ return output .NewOutput (os .Stdout , output.OutputOpts {})
305327}
0 commit comments