gastownhall/beads · error

failed to list projects: %w

Error message

failed to list projects: %w

What it means

Wraps a failure from client.ListProjects(ctx) in runADOProjects, i.e. the HTTP call to Azure DevOps to enumerate projects failed after the client was built successfully. The cause (network error, 401/403, 404 wrong org) is preserved via %w. This is a runtime/API-layer error, not a configuration error.

Source

Thrown at cmd/bd/ado.go:442

	cfg := getADOConfig()
	if cfg.PAT == "" {
		return fmt.Errorf("ado.pat not configured: set via 'bd config set ado.pat <token>' or AZURE_DEVOPS_PAT env var")
	}
	if cfg.Org == "" && cfg.URL == "" {
		return fmt.Errorf("ado.org not configured: set via 'bd config set ado.org <org>' or AZURE_DEVOPS_ORG env var")
	}

	out := cmd.OutOrStdout()
	client, err := getADOClient(cfg)
	if err != nil {
		return fmt.Errorf("invalid ADO configuration: %w", err)
	}
	ctx := context.Background()

	projects, err := client.ListProjects(ctx)
	if err != nil {
		return fmt.Errorf("failed to list projects: %w", err)
	}

	if jsonOutput {
		return outputJSON(projects)
	}

	_, _ = fmt.Fprintln(out, "Azure DevOps Projects")
	_, _ = fmt.Fprintln(out, "=====================")
	for _, p := range projects {
		_, _ = fmt.Fprintf(out, "  %s\n", p.Name)
		if p.Description != "" {
			_, _ = fmt.Fprintf(out, "    %s\n", p.Description)
		}
	}

	if len(projects) == 0 {
		_, _ = fmt.Fprintln(out, "No projects found")
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the wrapped message for the HTTP status or transport error.
  2. If 401/203: regenerate the PAT in Azure DevOps (must include Project/team read scope) and `bd config set ado.pat <token>` or update AZURE_DEVOPS_PAT.
  3. If 404: verify the org name via `bd config get ado.org`.
  4. If network/proxy: check connectivity to https://dev.azure.com and proxy env vars (HTTPS_PROXY).
  5. Retry after fixing; transient 5xx can be retried as-is.

Example fix

// before
Error: failed to list projects: ADO API error: TF400813 Resource unavailable: unauthorized

// after
$ bd config set ado.pat <new-pat-with-read-scope>
$ bd ado projects
Defensive patterns

Strategy: try-catch

Validate before calling

if os.Getenv("AZURE_DEVOPS_PAT") == "" && cfg.PAT == "" {
	return errors.New("PAT not configured; skipping ADO call")
}
// optional pre-flight:
resp, err := http.Head("https://dev.azure.com/" + org)
if err != nil || resp.StatusCode >= 500 { return fmt.Errorf("ADO unreachable") }

Type guard

func isAuthError(err error) bool {
	return err != nil && (strings.Contains(err.Error(), "401") || strings.Contains(err.Error(), "unauthorized"))
}

Try / catch

projects, err := runADOProjects(cmd, args)
if err != nil && strings.Contains(err.Error(), "failed to list projects") {
	if isAuthError(err) {
		// regenerate PAT and retry once
	}
	if isTimeout(err) { /* backoff + retry */ }
	return fmt.Errorf("ado list failed: %w", err)
}

Prevention

When it happens

Trigger: Any ListProjects call failing: expired or unauthorized PAT (HTTP 401/203), PAT lacking scope, wrong org name (404), DNS/proxy failure, or a mock server in tests returning an error (TestADOProjectsHTTPError).

Common situations: PAT rotated or revoked in Azure DevOps; org renamed; corporate proxy blocking dev.azure.com; CI runner without network egress; typo in org so the endpoint 404s.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/f4d213e96ac465f9. Report an issue: GitHub.