{"record":{"id":"c3413493e6252d52","repo":"gastownhall/beads","slug":"failed-to-list-projects-w-c34134","errorCode":null,"errorMessage":"failed to list projects: %w","messagePattern":"failed to list projects: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/ado/client.go","lineNumber":572,"sourceCode":"\t\t\tOp:   \"remove\",\n\t\t\tPath: fmt.Sprintf(\"/relations/%d\", relationIndex),\n\t\t},\n\t}\n\turlStr := addAPIVersion(fmt.Sprintf(\"%s/wit/workitems/%d\", c.apiBase(), sourceID))\n\t_, err := c.doRequest(ctx, http.MethodPatch, urlStr, \"application/json-patch+json\", ops)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to remove work item link: %w\", err)\n\t}\n\treturn nil\n}\n\n// ListProjects returns all team projects in the organization.\n// This is an org-level endpoint, not project-scoped.\nfunc (c *Client) ListProjects(ctx context.Context) ([]Project, error) {\n\turlStr := addAPIVersion(c.orgBase() + \"/projects\")\n\trespBody, err := c.doRequest(ctx, http.MethodGet, urlStr, \"\", nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to list projects: %w\", err)\n\t}\n\n\tvar envelope listResponse\n\tif err := json.Unmarshal(respBody, &envelope); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse projects response: %w\", err)\n\t}\n\n\tvar projects []Project\n\tif err := json.Unmarshal(envelope.Value, &projects); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse projects value: %w\", err)\n\t}\n\treturn projects, nil\n}\n\n// GetWorkItemTypes returns the work item types available in the project.\nfunc (c *Client) GetWorkItemTypes(ctx context.Context) ([]WorkItemType, error) {\n\turlStr := addAPIVersion(c.apiBase() + \"/wit/workitemtypes\")\n\trespBody, err := c.doRequest(ctx, http.MethodGet, urlStr, \"\", nil)","sourceCodeStart":554,"sourceCodeEnd":590,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/ado/client.go#L554-L590","documentation":"This error wraps an HTTP failure from the org-level GET {_org}/_apis/projects endpoint used by ListProjects. It fires before any JSON decoding, meaning the request itself failed (transport error or non-success status). The original error is preserved via %w.","triggerScenarios":"Calling Client.ListProjects(ctx) when doRequest fails: unauthenticated/invalid PAT (401), wrong organization URL in client config (404/DNS failure), network outage, or ADO returning 203/throttling responses treated as failures.","commonSituations":"Misconfigured organization URL (e.g. missing https:// or wrong org name); PAT without scope for the org; offline/VPN-restricted environments; org renamed or deleted.","solutions":["Check the wrapped error's status code; 401 means fix the PAT, 404 usually means wrong org URL","Verify the client's organization base URL (orgBase) is correct and reachable","Regenerate the PAT with appropriate scope (project/team read access)","Test the endpoint manually: GET https://dev.azure.com/{org}/_apis/projects?api-version=... with the same PAT","Rule out network/proxy issues by curling the org URL from the same host"],"exampleFix":"// before\ncfg := ado.Config{OrgURL: \"dev.azure.com/myorg\"} // missing scheme\n// after\ncfg := ado.Config{OrgURL: \"https://dev.azure.com/myorg\"}\nprojects, err := client.ListProjects(ctx)","handlingStrategy":"try-catch","validationCode":"// verify org URL and PAT before calling\nif !strings.HasPrefix(orgURL, \"https://dev.azure.com/\") && !strings.Contains(orgURL, \".visualstudio.com\") {\n  return fmt.Errorf(\"suspicious org URL: %s\", orgURL)\n}\nresp, err := http.Get(orgURL + \"/_apis/projects?api-version=7.1\") // smoke test w/ auth header","typeGuard":null,"tryCatchPattern":"projects, err := client.ListProjects(ctx)\nif err != nil {\n  if strings.Contains(err.Error(), \"401\") || strings.Contains(err.Error(), \"TF\") {\n    return nil, fmt.Errorf(\"auth failed listing projects — check PAT scope/expiry: %w\", err)\n  }\n  return nil, err\n}","preventionTips":["Use the full https:// scheme in the organization URL","Keep PATs scoped and check expiry in long-running services","Smoke-test connectivity to dev.azure.com from the deployment environment","Cache project lists to reduce exposure to transient failures"],"tags":["azure-devops","http","projects","authentication"],"backgroundTag":"http-request-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}