apache/beam · error

response contains bad status

Error message

response contains bad status: [%v]

What it means

fhirio's extractBodyFrom validates the HTTP response from the FHIR store API with googleapi.CheckResponse before reading the body. When the response carries a non-2xx status (auth failure, not found, malformed request, server error), CheckResponse returns an error and this error wraps it together with the response's status line, so the caller sees both the HTTP status and the underlying API error details.

Solutions

  1. Check the wrapped googleapi error's code and body for the server-side detail (401/403 → fix credentials/IAM; 404 → fix store path; 400 → fix query)
  2. Verify the pipeline's service account has the required healthcare.fhirStore permissions (viewer/searcher roles)
  3. Validate the FHIR store resource path and search parameters against the API docs before retrying
  4. Wrap the call in retry with backoff for transient 5xx statuses

Example fix

// before
resp, err := client.Search(ctx, store, "Patient", badParams) // 400
// after
params := url.Values{}
params.Set("family", "Smith") // valid FHIR search parameter
resp, err := client.Search(ctx, validStorePath, "Patient", params)
if err != nil {
    var gerr *googleapi.Error
    if errors.As(err, &gerr) { log.Printf("status %d: %s", gerr.Code, gerr.Body) }
}
Defensive patterns

Strategy: try-catch

Validate before calling

func validateFHIRStorePath(storePath string) error {
    re := regexp.MustCompile(`^projects/[^/]+/locations/[^/]+/datasets/[^/]+/fhirStores/[^/]+$`)
    if !re.MatchString(storePath) {
        return fmt.Errorf("invalid FHIR store path: %s", storePath)
    }
    return nil
}

Try / catch

result, err := fhirio.Search(s, store, "Patient").Get(...)
if err != nil {
    var gerr *googleapi.Error
    if errors.As(err, &gerr) {
        switch gerr.Code {
        case 401, 403:
            log.Printf("auth/IAM issue: %v", gerr)
        case 404:
            log.Printf("store not found: %v", gerr)
        case 400:
            log.Printf("bad query: %v", gerr)
        default:
            // transient 5xx: retry with backoff
        }
    }
}

Prevention

When it happens

Trigger: Any fhirio transformation (search, read, executeBundle, searchResourcesPaginated) hitting a FHIR store that returns an HTTP error status: invalid FHIR store path, insufficient IAM permissions, bad query parameters (e.g. invalid search filter), quota exceeded, or transient 5xx from the service.

Common situations: Misconfigured FHIR store URL or dataset/location path; missing roles/healthcare.fhirStore* IAM bindings on the pipeline's service account; expired or missing OAuth credentials; a search query with unsupported or misspelled FHIR search parameters yielding 400; transient 500/503 during high load.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of apache/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/fda3ada697913341. Report an issue: GitHub.

Appendix: source

Thrown at sdks/go/pkg/beam/io/fhirio/common.go:59

	operationErrorCounterName   = baseMetricPrefix + "operation_error_count"
	operationSuccessCounterName = baseMetricPrefix + "operation_success_count"
	successCounterName          = baseMetricPrefix + "resource_success_count"
	pageTokenParameterKey       = "_page_token"
)

var backoffDuration = [...]time.Duration{time.Second, 5 * time.Second, 10 * time.Second, 15 * time.Second}

func executeAndRecordLatency[T any](ctx context.Context, latencyMs *beam.Distribution, executionSupplier func() (T, error)) (T, error) {
	timeBeforeReadRequest := time.Now()
	result, err := executionSupplier()
	latencyMs.Update(ctx, time.Since(timeBeforeReadRequest).Milliseconds())
	return result, err
}

func extractBodyFrom(response *http.Response) (string, error) {
	err := googleapi.CheckResponse(response)
	if err != nil {
		return "", errors.Wrapf(err, "response contains bad status: [%v]", response.Status)
	}

	bodyBytes, err := io.ReadAll(response.Body)
	if err != nil {
		return "", err
	}

	return string(bodyBytes), nil
}

type operationCounters struct {
	successCount, errorCount beam.Counter
}

func (c *operationCounters) setup(namespace string) {
	c.successCount = beam.NewCounter(namespace, operationSuccessCounterName)
	c.errorCount = beam.NewCounter(namespace, operationErrorCounterName)
}

View on GitHub (pinned to 12126d8942)