{"record":{"id":"534026f8e77151a0","repo":"github/github-mcp-server","slug":"invalid-issue-url-q-w","errorCode":null,"errorMessage":"invalid issue URL %q: %w","messagePattern":"invalid issue URL %q: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/github/issues.go","lineNumber":1379,"sourceCode":"\t\t\t\tresult = reactionResponse\n\t\t\tdefault:\n\t\t\t\tresult = commentResponse\n\t\t\t}\n\n\t\t\tr, err := json.Marshal(result)\n\t\t\tif err != nil {\n\t\t\t\treturn utils.NewToolResultErrorFromErr(\"failed to marshal response\", err), nil, nil\n\t\t\t}\n\n\t\t\treturn utils.NewToolResultText(string(r)), nil, nil\n\t\t})\n}\n\nfunc issueNumberFromIssueURL(issueURL string) (int, error) {\n\tissueNumberString := issueURL[strings.LastIndex(issueURL, \"/\")+1:]\n\tissueNumber, err := strconv.Atoi(issueNumberString)\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"invalid issue URL %q: %w\", issueURL, err)\n\t}\n\treturn issueNumber, nil\n}\n\n// SubIssueWrite creates a tool to add a sub-issue to a parent issue.\nfunc SubIssueWrite(t translations.TranslationHelperFunc) inventory.ServerTool {\n\tst := NewTool(\n\t\tToolsetMetadataIssues,\n\t\tmcp.Tool{\n\t\t\tName:        \"sub_issue_write\",\n\t\t\tDescription: t(\"TOOL_SUB_ISSUE_WRITE_DESCRIPTION\", \"Add a sub-issue to a parent issue in a GitHub repository.\"),\n\t\t\tAnnotations: &mcp.ToolAnnotations{\n\t\t\t\tTitle:        t(\"TOOL_SUB_ISSUE_WRITE_USER_TITLE\", \"Change sub-issue\"),\n\t\t\t\tReadOnlyHint: false,\n\t\t\t},\n\t\t\tInputSchema: &jsonschema.Schema{\n\t\t\t\tType: \"object\",\n\t\t\t\tProperties: map[string]*jsonschema.Schema{","sourceCodeStart":1361,"sourceCodeEnd":1397,"githubUrl":"https://github.com/github/github-mcp-server/blob/0ea1f775a7c73eff1bd2e25904d01136756bbfe2/pkg/github/issues.go#L1361-L1397","documentation":"issueNumberFromIssueURL extracts everything after the last '/' in a string and runs strconv.Atoi on it. The error fires when that trailing segment is not a bare integer. Callers pass comment.GetIssueURL() — the issue_url field from GitHub's comment API — so failures mean the URL was empty, carried a trailing slash, or had a non-numeric suffix. Notably LastIndex returns -1 on a slash-free string, making Atoi parse the whole input.","triggerScenarios":"issue_url is empty (\"\"); URL has a trailing slash so the last segment is \"\"; URL ends with a query string or fragment instead of the number; a caller passes a number-less string like \"https://github.com/o/r/issues/\".","commonSituations":"API responses where issue_url is unexpectedly absent for deleted/anonymized issues; callers passing hand-built or copy-pasted URLs with trailing slashes; schema changes in comment payloads after GitHub API version bumps.","solutions":["Trim trailing slashes and query/fragment before extracting the number","Parse the URL properly with net/url and take the last numeric path segment","If the input is already a bare number, skip URL parsing entirely","Validate with a regexp like ^https?://[^/]+/[^/]+/[^/]+/issues/\\d+$ before calling"],"exampleFix":"// before\nissueNumberString := issueURL[strings.LastIndex(issueURL, \"/\")+1:]\nissueNumber, err := strconv.Atoi(issueNumberString)\n\n// after\nu, err := url.Parse(issueURL)\nif err != nil {\n    return 0, fmt.Errorf(\"invalid issue URL %q: %w\", issueURL, err)\n}\nseg := strings.Trim(u.Path, \"/\")\nissueNumber, err := strconv.Atoi(seg[strings.LastIndex(seg, \"/\")+1:])\nif err != nil {\n    return 0, fmt.Errorf(\"invalid issue URL %q: %w\", issueURL, err)\n}","handlingStrategy":"validation","validationCode":"// Validate before calling anything that derives a number from a URL\nvar issueURLRe = regexp.MustCompile(`^https?://[^/]+/[^/]+/[^/]+/issues/(\\d+|)$`)\nfunc isValidIssueURL(u string) bool {\n    u = strings.TrimRight(u, \"/?#\")\n    return issueURLRe.MatchString(strings.TrimSuffix(u, \"/\") + \"/\") || regexp.MustCompile(`^\\d+$`).MatchString(u)\n}","typeGuard":"var issueNumRe = regexp.MustCompile(`/(?:issues)/(\\d+)(?:[/?#].*)?$`)\nfunc issueNumberFromURL(u string) (int, bool) {\n    m := issueNumRe.FindStringSubmatch(u)\n    if m == nil { return 0, false }\n    n, err := strconv.Atoi(m[1])\n    return n, err == nil\n}","tryCatchPattern":"num, err := issueNumberFromIssueURL(rawURL)\nif err != nil {\n    if n, ok := issueNumberFromURL(strings.TrimRight(rawURL, \"/\")); ok {\n        num = n // recover from trailing slash / fragments\n    } else {\n        return fmt.Errorf(\"need an issue URL like .../issues/123: %w\", err)\n    }\n}","preventionTips":["Trim trailing '/', '?' and '#' from user- or API-supplied URLs before extracting segments","Prefer net/url parsing over LastIndex string surgery","Reject empty strings early — they parse as the whole string and always fail","Where the API offers a numeric field (e.g. comment.issue_number via GraphQL), take the number instead of parsing URLs"],"tags":["validation","url-parsing","comments","issues"],"backgroundTag":null,"analyzedSha":"0ea1f775a7c73eff1bd2e25904d01136756bbfe2","analyzedAt":"2026-08-15T18:10:19.804Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}