Skip to content

Commit d3d35f7

Browse files
fix/improve-error-messages
1 parent f404049 commit d3d35f7

File tree

3 files changed

+475
-74
lines changed

3 files changed

+475
-74
lines changed

cmd/src/code_intel_upload.go

Lines changed: 105 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -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,135 @@ 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).
219217
func handleUploadError(accessToken string, err error) error {
220-
if errors.Is(err, upload.ErrUnauthorized) {
221-
err = attachHintsForAuthorizationError(accessToken, err)
218+
if httpErr := findAuthError(err); httpErr != nil {
219+
isUnauthorized := httpErr.Code == 401
220+
isForbidden := httpErr.Code == 403
221+
222+
displayErr := errors.Newf("upload failed: %s", uploadFailureReason(httpErr))
223+
224+
err = errorWithHint{
225+
err: displayErr,
226+
hint: uploadHints(accessToken, isUnauthorized, isForbidden),
227+
}
222228
}
223229

224230
if codeintelUploadFlags.ignoreUploadFailures {
225-
// Report but don't return the error
226231
fmt.Println(err.Error())
227232
return nil
228233
}
229234

230235
return err
231236
}
232237

233-
func attachHintsForAuthorizationError(accessToken string, originalError error) error {
234-
var actionableHints []string
238+
// findAuthError searches the error chain (including multi-errors from retries)
239+
// for a 401 or 403 ErrUnexpectedStatusCode. Returns nil if none is found.
240+
func findAuthError(err error) *ErrUnexpectedStatusCode {
241+
// Check if it's a multi-error and scan all children.
242+
if multi, ok := err.(errors.MultiError); ok {
243+
for _, e := range multi.Errors() {
244+
if found := findAuthError(e); found != nil {
245+
return found
246+
}
247+
}
248+
return nil
249+
}
250+
251+
var httpErr *ErrUnexpectedStatusCode
252+
if errors.As(err, &httpErr) && (httpErr.Code == 401 || httpErr.Code == 403) {
253+
return httpErr
254+
}
255+
return nil
256+
}
257+
258+
// uploadHints builds hint paragraphs for the Sourcegraph access token,
259+
// code host tokens, and a docs link.
260+
func uploadHints(accessToken string, isUnauthorized, isForbidden bool) string {
261+
var causes []string
235262

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>.")
263+
if h := sourcegraphAccessTokenHint(accessToken, isUnauthorized, isForbidden); h != "" {
264+
causes = append(causes, "- "+h)
242265
}
243266

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")}
267+
for _, h := range codeHostTokenHints(isUnauthorized) {
268+
causes = append(causes, "- "+h)
250269
}
251270

252-
needsGitHubToken := strings.HasPrefix(codeintelUploadFlags.repo, "github.com")
253-
needsGitLabToken := strings.HasPrefix(codeintelUploadFlags.repo, "gitlab.com")
271+
var parts []string
272+
parts = append(parts, "Possible causes:\n"+strings.Join(causes, "\n"))
273+
parts = append(parts, "For more details on uploading SCIP indexes, see https://sourcegraph.com/docs/cli/references/code-intel/upload.")
254274

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-
)
275+
return strings.Join(parts, "\n\n")
276+
}
277+
278+
// sourcegraphAccessTokenHint returns a hint about the Sourcegraph access token
279+
// based on the error type and token state.
280+
func sourcegraphAccessTokenHint(accessToken string, isUnauthorized, isForbidden bool) string {
281+
if isUnauthorized {
282+
if accessToken == "" {
283+
return "No Sourcegraph access token was provided. Set the SRC_ACCESS_TOKEN environment variable to a valid token."
266284
}
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-
)
285+
if _, parseErr := accesstoken.ParsePersonalAccessToken(accessToken); parseErr != nil {
286+
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?"
278287
}
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-
)
288+
return "The Sourcegraph access token may be invalid, expired, or you may be connecting to the wrong Sourcegraph instance."
289+
}
290+
if isForbidden {
291+
return "You may not have sufficient permissions on this Sourcegraph instance."
284292
}
293+
return ""
294+
}
285295

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")}
296+
// codeHostTokenHints returns hints about GitHub or GitLab tokens.
297+
func codeHostTokenHints(isUnauthorized bool) []string {
298+
if codeintelUploadFlags.gitHubToken != "" || strings.HasPrefix(codeintelUploadFlags.repo, "github.com") {
299+
return []string{gitHubTokenHint(isUnauthorized)}
300+
}
301+
if codeintelUploadFlags.gitLabToken != "" || strings.HasPrefix(codeintelUploadFlags.repo, "gitlab.com") {
302+
return []string{gitLabTokenHint(isUnauthorized)}
303+
}
304+
return []string{"Code host verification is supported for github.com and gitlab.com repositories."}
291305
}
292306

293-
// emergencyOutput creates a default Output object writing to standard out.
294-
func emergencyOutput() *output.Output {
295-
return output.NewOutput(os.Stdout, output.OutputOpts{})
307+
// gitHubTokenHint returns a hint about the GitHub token.
308+
// Only called when gitHubToken is set or repo starts with "github.com".
309+
func gitHubTokenHint(isUnauthorized bool) string {
310+
if codeintelUploadFlags.gitHubToken == "" {
311+
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)
312+
}
313+
if isUnauthorized {
314+
return "The supplied -github-token may be invalid."
315+
}
316+
return "The supplied -github-token may lack the required permissions."
296317
}
297318

298-
func mergeStringSlices(ss ...[]string) []string {
299-
var combined []string
300-
for _, s := range ss {
301-
combined = append(combined, s...)
319+
// gitLabTokenHint returns a hint about the GitLab token.
320+
// Only called when gitLabToken is set or repo starts with "gitlab.com".
321+
func gitLabTokenHint(isUnauthorized bool) string {
322+
if codeintelUploadFlags.gitLabToken == "" {
323+
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)
324+
}
325+
if isUnauthorized {
326+
return "The supplied -gitlab-token may be invalid."
302327
}
328+
return "The supplied -gitlab-token may lack the required permissions."
329+
}
330+
331+
// uploadFailureReason returns the server's response body if available, or a
332+
// generic reason derived from the HTTP status code.
333+
func uploadFailureReason(httpErr *ErrUnexpectedStatusCode) string {
334+
if httpErr.Body != "" {
335+
return httpErr.Body
336+
}
337+
if httpErr.Code == 401 {
338+
return "unauthorized"
339+
}
340+
return "forbidden"
341+
}
303342

304-
return combined
343+
// emergencyOutput creates a default Output object writing to standard out.
344+
func emergencyOutput() *output.Output {
345+
return output.NewOutput(os.Stdout, output.OutputOpts{})
305346
}

0 commit comments

Comments
 (0)