argoproj/argo-workflows · error

%s: %s

Error message

%s: %s

What it means

A plugin HTTP call received a response with an unexpected status code (not 2xx and not the handled transient status). The plugin executor surfaces the HTTP status line plus the raw response body as the error, e.g. '500 Internal Server Error: <body>'. It tells you the plugin's own HTTP endpoint rejected the request.

Source

Thrown at workflow/util/plugin/plugin.go:93

		case http.StatusOK:
			return json.NewDecoder(resp.Body).Decode(reply)
		case http.StatusNotFound:
			log.Info(ctx, "method not found, not calling again")
			p.invalid[method] = true
			_, err := io.Copy(io.Discard, resp.Body)
			return err
		case http.StatusServiceUnavailable:
			data, err := io.ReadAll(resp.Body)
			if err != nil {
				return err
			}
			return errors.NewErrTransient(string(data))
		default:
			data, err := io.ReadAll(resp.Body)
			if err != nil {
				return err
			}
			return fmt.Errorf("%s: %s", resp.Status, string(data))
		}
	})
}

View on GitHub (pinned to 35bff19146)

Solutions

  1. Read the body in the error message — it usually contains the plugin's own error explaining the failure
  2. Verify the plugin endpoint URL/port in the plugin ConfigMap matches the running plugin service
  3. Check the plugin's logs for the corresponding request failure and fix server-side (auth, crash, bad payload)
  4. If the status indicates a transient condition in your plugin, return the recognized transient status so Argo retries

Example fix

// before (plugin handler returns bare 500)
w.WriteHeader(500)
// after (actionable body)
http.Error(w, "executor plugin: failed to load template: missing arg", 500)
Defensive patterns

Strategy: retry

Validate before calling

// preflight: check the plugin endpoint is reachable
resp, err := http.Get(pluginURL + "/health")
if err != nil || resp.StatusCode != 200 {
    return fmt.Errorf("plugin endpoint %s unhealthy", pluginURL)
}

Try / catch

err := executor.CallPlugin(ctx, ...)
if err != nil {
    var statusErr interface{ HTTPStatus() int }
    if strings.Contains(err.Error(), ": ") && !util_errors.IsTransientErr(ctx, err) {
        // non-transient HTTP failure: check plugin logs, do not retry blindly
    }
}

Prevention

When it happens

Trigger: workflow/executor/plugin driver posts a request to a plugin's HTTP endpoint and the response status falls into the default switch case — any non-handled status (e.g. 400, 403, 404, 500) — and the status+body are wrapped via fmt.Errorf("%s: %s", resp.Status, data).

Common situations: Plugin service not deployed/misconfigured (404), plugin auth failing (401/403), plugin crashing on the request (500), wrong port/URL in the plugin config; non-2xx statuses other than the recognized transient one.

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 argoproj/argo-workflows@35bff19146 (2026-09-03). Data as JSON: /api/errors/b05a3f9c9b1c4044. Report an issue: GitHub.